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 break;
2782
2783 case WXK_F3: {
2784 SetShowENCText(!GetShowENCText());
2785 Refresh(true);
2786 InvalidateGL();
2787 break;
2788 }
2789 case WXK_F4:
2790 if (!m_bMeasure_Active) {
2791 if (event.ShiftDown())
2792 m_bMeasure_DistCircle = true;
2793 else
2794 m_bMeasure_DistCircle = false;
2795
2796 StartMeasureRoute();
2797 } else {
2798 CancelMeasureRoute();
2799
2800 SetCursor(*pCursorArrow);
2801
2802 // SurfaceToolbar();
2803 InvalidateGL();
2804 Refresh(false);
2805 }
2806
2807 break;
2808
2809 case WXK_F5:
2810 parent_frame->ToggleColorScheme();
2811 gFrame->Raise();
2812 TriggerDeferredFocus();
2813 break;
2814
2815 case WXK_F6: {
2816 int mod = m_modkeys & wxMOD_SHIFT;
2817 if (mod != m_brightmod) {
2818 m_brightmod = mod;
2819 m_bbrightdir = !m_bbrightdir;
2820 }
2821
2822 if (!m_bbrightdir) {
2823 g_nbrightness -= 10;
2824 if (g_nbrightness <= MIN_BRIGHT) {
2825 g_nbrightness = MIN_BRIGHT;
2826 m_bbrightdir = true;
2827 }
2828 } else {
2829 g_nbrightness += 10;
2830 if (g_nbrightness >= MAX_BRIGHT) {
2831 g_nbrightness = MAX_BRIGHT;
2832 m_bbrightdir = false;
2833 }
2834 }
2835
2836 SetScreenBrightness(g_nbrightness);
2837 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2838
2839 SetFocus(); // just in case the external program steals it....
2840 gFrame->Raise(); // And reactivate the application main
2841
2842 break;
2843 }
2844
2845 case WXK_F7:
2846 parent_frame->DoStackDown(this);
2847 break;
2848
2849 case WXK_F8:
2850 parent_frame->DoStackUp(this);
2851 break;
2852
2853#ifndef __WXOSX__
2854 case WXK_F9: {
2855 ToggleCanvasQuiltMode();
2856 break;
2857 }
2858#endif
2859
2860 case WXK_F11:
2861 parent_frame->ToggleFullScreen();
2862 b_handled = true;
2863 break;
2864
2865 case WXK_F12: {
2866 if (m_modkeys == wxMOD_ALT) {
2867 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2868 } else {
2869 ToggleChartOutlines();
2870 }
2871 break;
2872 }
2873
2874 case WXK_PAUSE: // Drop MOB
2875 parent_frame->ActivateMOB();
2876 break;
2877
2878 // NUMERIC PAD
2879 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2880 case WXK_PAGEUP: {
2881 ZoomCanvas(g_plus_minus_zoom_factor, false);
2882 break;
2883 }
2884 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2885 case WXK_PAGEDOWN: {
2886 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2887 break;
2888 }
2889 case WXK_DELETE:
2890 case WXK_BACK:
2891 if (m_bMeasure_Active) {
2892 if (m_nMeasureState > 2) {
2893 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2894 m_pMeasureRoute->m_lastMousePointIndex =
2895 m_pMeasureRoute->GetnPoints();
2896 m_nMeasureState--;
2897 gFrame->RefreshAllCanvas();
2898 } else {
2899 CancelMeasureRoute();
2900 StartMeasureRoute();
2901 }
2902 }
2903 break;
2904 default:
2905 break;
2906 }
2907
2908 if (event.GetKeyCode() < 128) // ascii
2909 {
2910 int key_char = event.GetKeyCode();
2911
2912 // Handle both QWERTY and AZERTY keyboard separately for a few control
2913 // codes
2914 if (!g_b_assume_azerty) {
2915#ifdef __WXMAC__
2916 if (g_benable_rotate) {
2917 switch (key_char) {
2918 // On other platforms these are handled in OnKeyChar, which
2919 // (apparently) works better in some locales. On OS X it is better
2920 // to handle them here, since pressing Alt (which should change the
2921 // rotation speed) changes the key char and so prevents the keys
2922 // from working.
2923 case ']':
2924 RotateCanvas(1);
2925 b_handled = true;
2926 break;
2927
2928 case '[':
2929 RotateCanvas(-1);
2930 b_handled = true;
2931 break;
2932
2933 case '\\':
2934 DoRotateCanvas(0);
2935 b_handled = true;
2936 break;
2937 }
2938 }
2939#endif
2940 } else { // AZERTY
2941 switch (key_char) {
2942 case 43:
2943 ZoomCanvas(g_plus_minus_zoom_factor, false);
2944 break;
2945
2946 case 54: // '-' alpha/num pad
2947 // case 56: // '_' alpha/num pad
2948 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2949 break;
2950 }
2951 }
2952
2953#ifdef __WXOSX__
2954 // Ctrl+Cmd+F toggles fullscreen on macOS
2955 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
2956 m_modkeys & wxMOD_RAW_CONTROL) {
2957 parent_frame->ToggleFullScreen();
2958 return;
2959 }
2960#endif
2961
2962 if (event.ControlDown()) key_char -= 64;
2963
2964 if (key_char >= '0' && key_char <= '9')
2965 SetGroupIndex(key_char - '0');
2966 else
2967
2968 switch (key_char) {
2969 case 'A':
2970 SetShowENCAnchor(!GetShowENCAnchor());
2971 ReloadVP();
2972
2973 break;
2974
2975 case 'C':
2976 parent_frame->ToggleColorScheme();
2977 break;
2978
2979 case 'D': {
2980 int x, y;
2981 event.GetPosition(&x, &y);
2982 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
2983 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
2984 // First find out what kind of chart is being used
2985 if (!pPopupDetailSlider) {
2986 if (VPoint.b_quilt) {
2987 if (m_pQuilt) {
2988 if (m_pQuilt->GetChartAtPix(
2989 VPoint,
2990 wxPoint(
2991 x, y))) // = null if no chart loaded for this point
2992 {
2993 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2994 ->GetChartType();
2995 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2996 ->GetChartFamily();
2997 }
2998 }
2999 } else {
3000 if (m_singleChart) {
3001 ChartType = m_singleChart->GetChartType();
3002 ChartFam = m_singleChart->GetChartFamily();
3003 }
3004 }
3005 // If a charttype is found show the popupslider
3006 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3007 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3009 this, -1, ChartType, ChartFam,
3010 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3011 wxDefaultSize, wxSIMPLE_BORDER, "");
3013 }
3014 } else //( !pPopupDetailSlider ) close popupslider
3015 {
3017 pPopupDetailSlider = NULL;
3018 }
3019 break;
3020 }
3021
3022 case 'E':
3023 m_nmea_log->Show();
3024 m_nmea_log->Raise();
3025 break;
3026
3027 case 'L':
3028 SetShowENCLights(!GetShowENCLights());
3029 ReloadVP();
3030
3031 break;
3032
3033 case 'M':
3034 if (event.ShiftDown())
3035 m_bMeasure_DistCircle = true;
3036 else
3037 m_bMeasure_DistCircle = false;
3038
3039 StartMeasureRoute();
3040 break;
3041
3042 case 'N':
3043 if (g_bInlandEcdis && ps52plib) {
3044 SetENCDisplayCategory((_DisCat)STANDARD);
3045 }
3046 break;
3047
3048 case 'O':
3049 ToggleChartOutlines();
3050 break;
3051
3052 case 'Q':
3053 ToggleCanvasQuiltMode();
3054 break;
3055
3056 case 'P':
3057 parent_frame->ToggleTestPause();
3058 break;
3059 case 'R':
3060 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3061 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3062 g_iNavAidRadarRingsNumberVisible = 1;
3063 else if (!g_bNavAidRadarRingsShown &&
3064 g_iNavAidRadarRingsNumberVisible == 1)
3065 g_iNavAidRadarRingsNumberVisible = 0;
3066 break;
3067 case 'S':
3068 SetShowENCDepth(!m_encShowDepth);
3069 ReloadVP();
3070 break;
3071
3072 case 'T':
3073 SetShowENCText(!GetShowENCText());
3074 ReloadVP();
3075 break;
3076
3077 case 'U':
3078 SetShowENCDataQual(!GetShowENCDataQual());
3079 ReloadVP();
3080 break;
3081
3082 case 'V':
3083 m_bShowNavobjects = !m_bShowNavobjects;
3084 Refresh(true);
3085 break;
3086
3087 case 'W': // W Toggle CPA alarm
3088 ToggleCPAWarn();
3089
3090 break;
3091
3092 case 1: // Ctrl A
3093 TogglebFollow();
3094
3095 break;
3096
3097 case 2: // Ctrl B
3098 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3099 break;
3100
3101 case 13: // Ctrl M // Drop Marker at cursor
3102 {
3103 if (event.ControlDown()) gFrame->DropMarker(false);
3104 break;
3105 }
3106
3107 case 14: // Ctrl N - Activate next waypoint in a route
3108 {
3109 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3110 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3111 if ((indexActive + 1) <= r->GetnPoints()) {
3113 InvalidateGL();
3114 Refresh(false);
3115 }
3116 }
3117 break;
3118 }
3119
3120 case 15: // Ctrl O - Drop Marker at boat's position
3121 {
3122 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3123 break;
3124 }
3125
3126 case 32: // Special needs use space bar
3127 {
3128 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3129 break;
3130 }
3131
3132 case -32: // Ctrl Space // Drop MOB
3133 {
3134 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3135
3136 break;
3137 }
3138
3139 case -20: // Ctrl ,
3140 {
3141 parent_frame->DoSettings();
3142 break;
3143 }
3144 case 17: // Ctrl Q
3145 parent_frame->Close();
3146 return;
3147
3148 case 18: // Ctrl R
3149 StartRoute();
3150 return;
3151
3152 case 20: // Ctrl T
3153 if (NULL == pGoToPositionDialog) // There is one global instance of
3154 // the Go To Position Dialog
3156 pGoToPositionDialog->SetCanvas(this);
3157 pGoToPositionDialog->Show();
3158 break;
3159
3160 case 25: // Ctrl Y
3161 if (undo->AnythingToRedo()) {
3162 undo->RedoNextAction();
3163 InvalidateGL();
3164 Refresh(false);
3165 }
3166 break;
3167
3168 case 26:
3169 if (event.ShiftDown()) { // Shift-Ctrl-Z
3170 if (undo->AnythingToRedo()) {
3171 undo->RedoNextAction();
3172 InvalidateGL();
3173 Refresh(false);
3174 }
3175 } else { // Ctrl Z
3176 if (undo->AnythingToUndo()) {
3177 undo->UndoLastAction();
3178 InvalidateGL();
3179 Refresh(false);
3180 }
3181 }
3182 break;
3183
3184 case 27:
3185 // Generic break
3186 if (m_bMeasure_Active) {
3187 CancelMeasureRoute();
3188
3189 SetCursor(*pCursorArrow);
3190
3191 // SurfaceToolbar();
3192 gFrame->RefreshAllCanvas();
3193 }
3194
3195 if (m_routeState) // creating route?
3196 {
3197 FinishRoute();
3198 // SurfaceToolbar();
3199 InvalidateGL();
3200 Refresh(false);
3201 }
3202
3203 break;
3204
3205 case 7: // Ctrl G
3206 switch (gamma_state) {
3207 case (0):
3208 r_gamma_mult = 0;
3209 g_gamma_mult = 1;
3210 b_gamma_mult = 0;
3211 gamma_state = 1;
3212 break;
3213 case (1):
3214 r_gamma_mult = 1;
3215 g_gamma_mult = 0;
3216 b_gamma_mult = 0;
3217 gamma_state = 2;
3218 break;
3219 case (2):
3220 r_gamma_mult = 1;
3221 g_gamma_mult = 1;
3222 b_gamma_mult = 1;
3223 gamma_state = 0;
3224 break;
3225 }
3226 SetScreenBrightness(g_nbrightness);
3227
3228 break;
3229
3230 case 9: // Ctrl I
3231 if (event.ControlDown()) {
3232 m_bShowCompassWin = !m_bShowCompassWin;
3233 SetShowGPSCompassWindow(m_bShowCompassWin);
3234 Refresh(false);
3235 }
3236 break;
3237
3238 default:
3239 break;
3240
3241 } // switch
3242 }
3243
3244 // Allow OnKeyChar to catch the key events too.
3245 if (!b_handled) {
3246 event.Skip();
3247 }
3248}
3249
3250void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3251 if (SendKeyEventToPlugins(event))
3252 return; // PlugIn did something, and does not want the canvas to do
3253 // anything else
3254
3255 switch (event.GetKeyCode()) {
3256 case WXK_TAB:
3257 parent_frame->SwitchKBFocus(this);
3258 break;
3259
3260 case WXK_LEFT:
3261 case WXK_RIGHT:
3262 m_panx = 0;
3263 if (!m_pany) m_panspeed = 0;
3264 break;
3265
3266 case WXK_UP:
3267 case WXK_DOWN:
3268 m_pany = 0;
3269 if (!m_panx) m_panspeed = 0;
3270 break;
3271
3272 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3273 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3274 case WXK_PAGEUP:
3275 case WXK_PAGEDOWN:
3276 if (m_mustmove) DoMovement(m_mustmove);
3277
3278 m_zoom_factor = 1;
3279 break;
3280
3281 case WXK_ALT:
3282 m_modkeys &= ~wxMOD_ALT;
3283#ifdef OCPN_ALT_MENUBAR
3284#ifndef __WXOSX__
3285 // If the permanent menu bar is disabled, and we are not in the middle of
3286 // another key combo, then show the menu bar temporarily when Alt is
3287 // released (or hide it if already visible).
3288 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3289 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3290 parent_frame->ApplyGlobalSettings(false);
3291 }
3292 m_bMayToggleMenuBar = true;
3293#endif
3294#endif
3295 break;
3296
3297 case WXK_CONTROL:
3298 m_modkeys &= ~wxMOD_CONTROL;
3299 break;
3300 }
3301
3302 if (event.GetKeyCode() < 128) // ascii
3303 {
3304 int key_char = event.GetKeyCode();
3305
3306 // Handle both QWERTY and AZERTY keyboard separately for a few control
3307 // codes
3308 if (!g_b_assume_azerty) {
3309 switch (key_char) {
3310 case '+':
3311 case '=':
3312 case '-':
3313 case '_':
3314 case 54:
3315 case 56: // '_' alpha/num pad
3316 DoMovement(m_mustmove);
3317
3318 // m_zoom_factor = 1;
3319 break;
3320 case '[':
3321 case ']':
3322 DoMovement(m_mustmove);
3323 m_rotation_speed = 0;
3324 break;
3325 }
3326 } else {
3327 switch (key_char) {
3328 case 43:
3329 case 54: // '-' alpha/num pad
3330 case 56: // '_' alpha/num pad
3331 DoMovement(m_mustmove);
3332
3333 m_zoom_factor = 1;
3334 break;
3335 }
3336 }
3337 }
3338 event.Skip();
3339}
3340
3341void ChartCanvas::ToggleChartOutlines() {
3342 m_bShowOutlines = !m_bShowOutlines;
3343
3344 Refresh(false);
3345
3346#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3347 // needs a full refresh
3348 if (g_bopengl) InvalidateGL();
3349#endif
3350}
3351
3352void ChartCanvas::ToggleLookahead() {
3353 m_bLookAhead = !m_bLookAhead;
3354 m_OSoffsetx = 0; // center ownship
3355 m_OSoffsety = 0;
3356}
3357
3358void ChartCanvas::SetUpMode(int mode) {
3359 m_upMode = mode;
3360
3361 if (mode != NORTH_UP_MODE) {
3362 // Stuff the COGAvg table in case COGUp is selected
3363 double stuff = 0;
3364 if (!std::isnan(gCog)) stuff = gCog;
3365
3366 if (g_COGAvgSec > 0) {
3367 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3368 }
3369 g_COGAvg = stuff;
3370 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3371 } else {
3372 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3373 SetVPRotation(GetVPSkew());
3374 else
3375 SetVPRotation(0); /* reset to north up */
3376 }
3377
3378 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3379 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3380
3381 UpdateGPSCompassStatusBox(true);
3382 gFrame->DoChartUpdate();
3383}
3384
3385bool ChartCanvas::DoCanvasCOGSet() {
3386 if (GetUpMode() == NORTH_UP_MODE) return false;
3387 double cog_use = g_COGAvg;
3388 if (g_btenhertz) cog_use = gCog;
3389
3390 double rotation = 0;
3391 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3392 rotation = -gHdt * PI / 180.;
3393 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3394 rotation = -cog_use * PI / 180.;
3395
3396 SetVPRotation(rotation);
3397 return true;
3398}
3399
3400double easeOutCubic(double t) {
3401 // Starts quickly and slows down toward the end
3402 return 1.0 - pow(1.0 - t, 3.0);
3403}
3404
3405void ChartCanvas::StartChartDragInertia() {
3406 m_bChartDragging = false;
3407
3408 // Set some parameters
3409 m_chart_drag_inertia_time = 750; // msec
3410 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3411 m_last_elapsed = 0;
3412
3413 // Calculate ending drag velocity
3414 size_t n_vel = 10;
3415 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3416 int xacc = 0;
3417 int yacc = 0;
3418 double tacc = 0;
3419 size_t length = m_drag_vec_t.size();
3420 for (size_t i = 0; i < n_vel; i++) {
3421 xacc += m_drag_vec_x.at(length - 1 - i);
3422 yacc += m_drag_vec_y.at(length - 1 - i);
3423 tacc += m_drag_vec_t.at(length - 1 - i);
3424 }
3425
3426 if (tacc == 0) return;
3427
3428 double drag_velocity_x = xacc / tacc;
3429 double drag_velocity_y = yacc / tacc;
3430 // printf("drag total %d %d %g %g %g\n", xacc, yacc, tacc, drag_velocity_x,
3431 // drag_velocity_y);
3432
3433 // Abort inertia drag if velocity is very slow, preventing jitters on sloppy
3434 // touch tap.
3435 if ((fabs(drag_velocity_x) < 200) && (fabs(drag_velocity_y) < 200)) return;
3436
3437 m_chart_drag_velocity_x = drag_velocity_x;
3438 m_chart_drag_velocity_y = drag_velocity_y;
3439
3440 m_chart_drag_inertia_active = true;
3441 // First callback as fast as possible.
3442 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3443}
3444
3445void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3446 if (!m_chart_drag_inertia_active) return;
3447 // Calculate time fraction from 0..1
3448 wxLongLong now = wxGetLocalTimeMillis();
3449 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3450 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3451 if (t > 1.0) t = 1.0;
3452 double e = 1.0 - easeOutCubic(t); // 0..1
3453
3454 double dx =
3455 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3456 double dy =
3457 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3458
3459 m_last_elapsed = elapsed;
3460
3461 // Ensure that target destination lies on whole-pixel boundary
3462 // This allows the render engine to use a faster FBO copy method for drawing
3463 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3464 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3465 double inertia_lat, inertia_lon;
3466 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3467 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3468 // Check if ownship has moved off-screen
3469 if (!IsOwnshipOnScreen()) {
3470 m_bFollow = false; // update the follow flag
3471 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3472 UpdateFollowButtonState();
3473 m_OSoffsetx = 0;
3474 m_OSoffsety = 0;
3475 } else {
3476 m_OSoffsetx += dx;
3477 m_OSoffsety -= dy;
3478 }
3479
3480 Refresh(false);
3481
3482 // Stop condition
3483 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3484 m_chart_drag_inertia_timer.Stop();
3485
3486 // Disable chart pan movement logic
3487 m_target_lat = GetVP().clat;
3488 m_target_lon = GetVP().clon;
3489 m_pan_drag.x = m_pan_drag.y = 0;
3490 m_panx = m_pany = 0;
3491 m_chart_drag_inertia_active = false;
3492 DoCanvasUpdate();
3493
3494 } else {
3495 int target_redraw_interval = 40; // msec
3496 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3497 }
3498}
3499
3500void ChartCanvas::StopMovement() {
3501 m_panx = m_pany = 0;
3502 m_panspeed = 0;
3503 m_zoom_factor = 1;
3504 m_rotation_speed = 0;
3505 m_mustmove = 0;
3506#if 0
3507#if !defined(__WXGTK__) && !defined(__WXQT__)
3508 SetFocus();
3509 gFrame->Raise();
3510#endif
3511#endif
3512}
3513
3514/* instead of integrating in timer callbacks
3515 (which do not always get called fast enough)
3516 we can perform the integration of movement
3517 at each render frame based on the time change */
3518bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3519 // Start/restart the stop movement timer
3520 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3521
3522 if (!pMovementTimer->IsRunning()) {
3523 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3524 }
3525
3526 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3527 // already moving, gets called again because of key-repeat event
3528 return false;
3529 }
3530
3531 m_last_movement_time = wxDateTime::UNow();
3532
3533 return true;
3534}
3535void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3536 int nstep) {
3537 // Save the target
3538 m_target_lat = target_lat;
3539 m_target_lon = target_lon;
3540
3541 // Save the start point
3542 m_start_lat = GetVP().clat;
3543 m_start_lon = GetVP().clon;
3544
3545 m_VPMovementTimer.Start(1, true); // oneshot
3546 m_timed_move_vp_active = true;
3547 m_stvpc = 0;
3548 m_timedVP_step = nstep;
3549}
3550
3551void ChartCanvas::DoTimedMovementVP() {
3552 if (!m_timed_move_vp_active) return; // not active
3553 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3554 StopMovement();
3555 return;
3556 }
3557 // Stop condition
3558 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3559 double d2 =
3560 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3561 d2 = pow(d2, 0.5);
3562
3563 if (d2 < one_pix) {
3564 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3565 StopMovementVP();
3566 return;
3567 }
3568
3569 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3570 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3571 // StopMovementVP();
3572 // return;
3573 // }
3574
3575 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3576 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3577
3578 m_run_lat = new_lat;
3579 m_run_lon = new_lon;
3580
3581 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3582}
3583
3584void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3585
3586void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3587
3588void ChartCanvas::StartTimedMovementTarget() {}
3589
3590void ChartCanvas::DoTimedMovementTarget() {}
3591
3592void ChartCanvas::StopMovementTarget() {}
3593int ntm;
3594
3595void ChartCanvas::DoTimedMovement() {
3596 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3597 !m_rotation_speed)
3598 return; /* not moving */
3599
3600 wxDateTime now = wxDateTime::UNow();
3601 long dt = 0;
3602 if (m_last_movement_time.IsValid())
3603 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3604
3605 m_last_movement_time = now;
3606
3607 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3608 dt = 500;
3609
3610 DoMovement(dt);
3611}
3612
3614 /* if we get here quickly assume 1ms so that some movement occurs */
3615 if (dt == 0) dt = 1;
3616
3617 m_mustmove -= dt;
3618 if (m_mustmove < 0) m_mustmove = 0;
3619
3620 if (!m_inPinch) { // this stops compound zoom/pan
3621 if (m_pan_drag.x || m_pan_drag.y) {
3622 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3623 m_pan_drag.x = m_pan_drag.y = 0;
3624 }
3625
3626 if (m_panx || m_pany) {
3627 const double slowpan = .1, maxpan = 2;
3628 if (m_modkeys == wxMOD_ALT)
3629 m_panspeed = slowpan;
3630 else {
3631 m_panspeed += (double)dt / 500; /* apply acceleration */
3632 m_panspeed = wxMin(maxpan, m_panspeed);
3633 }
3634 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3635 }
3636 }
3637 if (m_zoom_factor != 1) {
3638 double alpha = 400, beta = 1.5;
3639 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3640
3641 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3642
3643 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3644
3645 // Try to hit the zoom target exactly.
3646 // if(m_wheelzoom_stop_oneshot > 0)
3647 {
3648 if (zoom_factor > 1) {
3649 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3650 zoom_factor = VPoint.chart_scale / m_zoom_target;
3651 }
3652
3653 else if (zoom_factor < 1) {
3654 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3655 zoom_factor = VPoint.chart_scale / m_zoom_target;
3656 }
3657 }
3658
3659 if (fabs(zoom_factor - 1) > 1e-4) {
3660 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3661 } else {
3662 StopMovement();
3663 }
3664
3665 if (m_wheelzoom_stop_oneshot > 0) {
3666 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3667 m_wheelzoom_stop_oneshot = 0;
3668 StopMovement();
3669 }
3670
3671 // Don't overshoot the zoom target.
3672 if (zoom_factor > 1) {
3673 if (VPoint.chart_scale <= m_zoom_target) {
3674 m_wheelzoom_stop_oneshot = 0;
3675 StopMovement();
3676 }
3677 } else if (zoom_factor < 1) {
3678 if (VPoint.chart_scale >= m_zoom_target) {
3679 m_wheelzoom_stop_oneshot = 0;
3680 StopMovement();
3681 }
3682 }
3683 }
3684 }
3685
3686 if (m_rotation_speed) { /* in degrees per second */
3687 double speed = m_rotation_speed;
3688 if (m_modkeys == wxMOD_ALT) speed /= 10;
3689 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3690 }
3691}
3692
3693void ChartCanvas::SetColorScheme(ColorScheme cs) {
3694 SetAlertString("");
3695
3696 // Setup ownship image pointers
3697 switch (cs) {
3698 case GLOBAL_COLOR_SCHEME_DAY:
3699 m_pos_image_red = &m_os_image_red_day;
3700 m_pos_image_grey = &m_os_image_grey_day;
3701 m_pos_image_yellow = &m_os_image_yellow_day;
3702 m_pos_image_user = m_pos_image_user_day;
3703 m_pos_image_user_grey = m_pos_image_user_grey_day;
3704 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3705 m_cTideBitmap = m_bmTideDay;
3706 m_cCurrentBitmap = m_bmCurrentDay;
3707
3708 break;
3709 case GLOBAL_COLOR_SCHEME_DUSK:
3710 m_pos_image_red = &m_os_image_red_dusk;
3711 m_pos_image_grey = &m_os_image_grey_dusk;
3712 m_pos_image_yellow = &m_os_image_yellow_dusk;
3713 m_pos_image_user = m_pos_image_user_dusk;
3714 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3715 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3716 m_cTideBitmap = m_bmTideDusk;
3717 m_cCurrentBitmap = m_bmCurrentDusk;
3718 break;
3719 case GLOBAL_COLOR_SCHEME_NIGHT:
3720 m_pos_image_red = &m_os_image_red_night;
3721 m_pos_image_grey = &m_os_image_grey_night;
3722 m_pos_image_yellow = &m_os_image_yellow_night;
3723 m_pos_image_user = m_pos_image_user_night;
3724 m_pos_image_user_grey = m_pos_image_user_grey_night;
3725 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3726 m_cTideBitmap = m_bmTideNight;
3727 m_cCurrentBitmap = m_bmCurrentNight;
3728 break;
3729 default:
3730 m_pos_image_red = &m_os_image_red_day;
3731 m_pos_image_grey = &m_os_image_grey_day;
3732 m_pos_image_yellow = &m_os_image_yellow_day;
3733 m_pos_image_user = m_pos_image_user_day;
3734 m_pos_image_user_grey = m_pos_image_user_grey_day;
3735 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3736 m_cTideBitmap = m_bmTideDay;
3737 m_cCurrentBitmap = m_bmCurrentDay;
3738 break;
3739 }
3740
3741 CreateDepthUnitEmbossMaps(cs);
3742 CreateOZEmbossMapData(cs);
3743
3744 // Set up fog effect base color
3745 m_fog_color = wxColor(
3746 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3747 float dim = 1.0;
3748 switch (cs) {
3749 case GLOBAL_COLOR_SCHEME_DUSK:
3750 dim = 0.5;
3751 break;
3752 case GLOBAL_COLOR_SCHEME_NIGHT:
3753 dim = 0.25;
3754 break;
3755 default:
3756 break;
3757 }
3758 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3759 m_fog_color.Blue() * dim);
3760
3761 // Really dark
3762#if 0
3763 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3764 SetBackgroundColour( wxColour(0,0,0) );
3765
3766 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3767 }
3768 else{
3769 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3770#ifndef __WXMAC__
3771 SetBackgroundColour( wxNullColour );
3772#endif
3773 }
3774#endif
3775
3776 // UpdateToolbarColorScheme(cs);
3777
3778 m_Piano->SetColorScheme(cs);
3779
3780 m_Compass->SetColorScheme(cs);
3781
3782 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3783
3784 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3785
3786 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3787 if (m_notification_button) {
3788 m_notification_button->SetColorScheme(cs);
3789 }
3790
3791#ifdef ocpnUSE_GL
3792 if (g_bopengl && m_glcc) {
3793 m_glcc->SetColorScheme(cs);
3794 g_glTextureManager->ClearAllRasterTextures();
3795 // m_glcc->FlushFBO();
3796 }
3797#endif
3798 SetbTCUpdate(true); // force re-render of tide/current locators
3799 m_brepaint_piano = true;
3800
3801 ReloadVP();
3802
3803 m_cs = cs;
3804}
3805
3806wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3807 wxImage img = Bitmap.ConvertToImage();
3808 int sx = img.GetWidth();
3809 int sy = img.GetHeight();
3810
3811 wxImage new_img(img);
3812
3813 for (int i = 0; i < sx; i++) {
3814 for (int j = 0; j < sy; j++) {
3815 if (!img.IsTransparent(i, j)) {
3816 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3817 (unsigned char)(img.GetGreen(i, j) * factor),
3818 (unsigned char)(img.GetBlue(i, j) * factor));
3819 }
3820 }
3821 }
3822
3823 wxBitmap ret = wxBitmap(new_img);
3824
3825 return ret;
3826}
3827
3828void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3829 int max) {
3830 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3831 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3832
3833 if (!m_pBrightPopup) {
3834 // Calculate size
3835 int x, y;
3836 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3837
3838 m_pBrightPopup = new TimedPopupWin(this, 3);
3839
3840 m_pBrightPopup->SetSize(x, y);
3841 m_pBrightPopup->Move(120, 120);
3842 }
3843
3844 int bmpsx = m_pBrightPopup->GetSize().x;
3845 int bmpsy = m_pBrightPopup->GetSize().y;
3846
3847 wxBitmap bmp(bmpsx, bmpsx);
3848 wxMemoryDC mdc(bmp);
3849
3850 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3851 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3852 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3853 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3854 mdc.Clear();
3855
3856 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3857
3858 mdc.SetFont(*pfont);
3859 wxString val;
3860
3861 if (brightness == max)
3862 val = "MAX";
3863 else if (brightness == min)
3864 val = "MIN";
3865 else
3866 val.Printf("%3d", brightness);
3867
3868 mdc.DrawText(val, 0, 0);
3869
3870 mdc.SelectObject(wxNullBitmap);
3871
3872 m_pBrightPopup->SetBitmap(bmp);
3873 m_pBrightPopup->Show();
3874 m_pBrightPopup->Refresh();
3875}
3876
3877void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3878 m_b_rot_hidef = true;
3879 ReloadVP();
3880}
3881
3882void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3883 if (!g_bRollover) return;
3884
3885 bool b_need_refresh = false;
3886
3887 wxSize win_size = GetSize() * m_displayScale;
3888 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3889
3890 // Handle the AIS Rollover Window first
3891 bool showAISRollover = false;
3892 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3893 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3894 SelectItem *pFind = pSelectAIS->FindSelection(
3895 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3896 if (pFind) {
3897 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3898 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3899
3900 if (ptarget) {
3901 showAISRollover = true;
3902
3903 if (NULL == m_pAISRolloverWin) {
3904 m_pAISRolloverWin = new RolloverWin(this);
3905 m_pAISRolloverWin->IsActive(false);
3906 b_need_refresh = true;
3907 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3908 m_AISRollover_MMSI != FoundAIS_MMSI) {
3909 // Sometimes the mouse moves fast enough to get over a new AIS
3910 // target before the one-shot has fired to remove the old target.
3911 // Result: wrong target data is shown.
3912 // Detect this case,close the existing rollover ASAP, and restart
3913 // the timer.
3914 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3915 m_pAISRolloverWin->IsActive(false);
3916 m_AISRollover_MMSI = 0;
3917 Refresh();
3918 return;
3919 }
3920
3921 m_AISRollover_MMSI = FoundAIS_MMSI;
3922
3923 if (!m_pAISRolloverWin->IsActive()) {
3924 wxString s = ptarget->GetRolloverString();
3925 m_pAISRolloverWin->SetString(s);
3926
3927 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3928 AIS_ROLLOVER, win_size);
3929 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3930 m_pAISRolloverWin->IsActive(true);
3931 b_need_refresh = true;
3932 }
3933 }
3934 } else {
3935 m_AISRollover_MMSI = 0;
3936 showAISRollover = false;
3937 }
3938 }
3939
3940 // Maybe turn the rollover off
3941 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3942 m_pAISRolloverWin->IsActive(false);
3943 m_AISRollover_MMSI = 0;
3944 b_need_refresh = true;
3945 }
3946
3947 // Now the Route info rollover
3948 // Show the route segment info
3949 bool showRouteRollover = false;
3950
3951 if (NULL == m_pRolloverRouteSeg) {
3952 // Get a list of all selectable sgements, and search for the first
3953 // visible segment as the rollover target.
3954
3955 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3956 SelectableItemList SelList = pSelect->FindSelectionList(
3957 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3958 auto node = SelList.begin();
3959 while (node != SelList.end()) {
3960 SelectItem *pFindSel = *node;
3961
3962 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3963
3964 if (pr && pr->IsVisible()) {
3965 m_pRolloverRouteSeg = pFindSel;
3966 showRouteRollover = true;
3967
3968 if (NULL == m_pRouteRolloverWin) {
3969 m_pRouteRolloverWin = new RolloverWin(this, 10);
3970 m_pRouteRolloverWin->IsActive(false);
3971 }
3972
3973 if (!m_pRouteRolloverWin->IsActive()) {
3974 wxString s;
3975 RoutePoint *segShow_point_a =
3976 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
3977 RoutePoint *segShow_point_b =
3978 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
3979
3980 double brg, dist;
3981 DistanceBearingMercator(
3982 segShow_point_b->m_lat, segShow_point_b->m_lon,
3983 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
3984
3985 if (!pr->m_bIsInLayer)
3986 s.Append(_("Route") + ": ");
3987 else
3988 s.Append(_("Layer Route: "));
3989
3990 if (pr->m_RouteNameString.IsEmpty())
3991 s.Append(_("(unnamed)"));
3992 else
3993 s.Append(pr->m_RouteNameString);
3994
3995 s << "\n"
3996 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
3997 << "\n"
3998 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
3999 << segShow_point_b->GetName() << "\n";
4000
4001 if (g_bShowTrue)
4002 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4003 (int)floor(brg + 0.5), 0x00B0);
4004 if (g_bShowMag) {
4005 double latAverage =
4006 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4007 double lonAverage =
4008 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4009 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4010
4011 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4012 (int)floor(varBrg + 0.5), 0x00B0);
4013 }
4014
4015 s << FormatDistanceAdaptive(dist);
4016
4017 // Compute and display cumulative distance from route start point to
4018 // current leg end point and RNG,TTG,ETA from ship to current leg end
4019 // point for active route
4020 double shiptoEndLeg = 0.;
4021 bool validActive = false;
4022 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4023 validActive = true;
4024
4025 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4026 auto node = pr->pRoutePointList->begin();
4027 RoutePoint *prp;
4028 float dist_to_endleg = 0;
4029 wxString t;
4030
4031 for (++node; node != pr->pRoutePointList->end(); ++node) {
4032 prp = *node;
4033 if (validActive)
4034 shiptoEndLeg += prp->m_seg_len;
4035 else if (prp->m_bIsActive)
4036 validActive = true;
4037 dist_to_endleg += prp->m_seg_len;
4038 if (prp->IsSame(segShow_point_a)) break;
4039 }
4040 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4041 }
4042 // write from ship to end selected leg point data if the route is
4043 // active
4044 if (validActive) {
4045 s << "\n"
4046 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4047 shiptoEndLeg +=
4049 ->GetCurrentRngToActivePoint(); // add distance from ship
4050 // to active point
4051 shiptoEndLeg +=
4052 segShow_point_b
4053 ->m_seg_len; // add the lenght of the selected leg
4054 s << FormatDistanceAdaptive(shiptoEndLeg);
4055 // ensure sog/cog are valid and vmg is positive to keep data
4056 // coherent
4057 double vmg = 0.;
4058 if (!std::isnan(gCog) && !std::isnan(gSog))
4059 vmg = gSog *
4060 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4061 PI / 180.);
4062 if (vmg > 0.) {
4063 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4064 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4065 s << " - "
4066 << wxString(ttg_sec > SECONDS_PER_DAY
4067 ? ttg_span.Format(_("%Dd %H:%M"))
4068 : ttg_span.Format(_("%H:%M")));
4069 wxDateTime dtnow, eta;
4070 eta = dtnow.SetToCurrent().Add(ttg_span);
4071 s << " - " << eta.Format("%b").Mid(0, 4)
4072 << eta.Format(" %d %H:%M");
4073 } else
4074 s << " ---- ----";
4075 }
4076 m_pRouteRolloverWin->SetString(s);
4077
4078 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4079 LEG_ROLLOVER, win_size);
4080 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4081 m_pRouteRolloverWin->IsActive(true);
4082 b_need_refresh = true;
4083 showRouteRollover = true;
4084 break;
4085 }
4086 } else {
4087 ++node;
4088 }
4089 }
4090 } else {
4091 // Is the cursor still in select radius, and not timed out?
4092 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4093 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4094 m_pRolloverRouteSeg))
4095 showRouteRollover = false;
4096 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4097 showRouteRollover = false;
4098 else
4099 showRouteRollover = true;
4100 }
4101
4102 // If currently creating a route, do not show this rollover window
4103 if (m_routeState) showRouteRollover = false;
4104
4105 // Similar for AIS target rollover window
4106 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4107 showRouteRollover = false;
4108
4109 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4110 !showRouteRollover) {
4111 m_pRouteRolloverWin->IsActive(false);
4112 m_pRolloverRouteSeg = NULL;
4113 m_pRouteRolloverWin->Destroy();
4114 m_pRouteRolloverWin = NULL;
4115 b_need_refresh = true;
4116 } else if (m_pRouteRolloverWin && showRouteRollover) {
4117 m_pRouteRolloverWin->IsActive(true);
4118 b_need_refresh = true;
4119 }
4120
4121 // Now the Track info rollover
4122 // Show the track segment info
4123 bool showTrackRollover = false;
4124
4125 if (NULL == m_pRolloverTrackSeg) {
4126 // Get a list of all selectable sgements, and search for the first
4127 // visible segment as the rollover target.
4128
4129 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4130 SelectableItemList SelList = pSelect->FindSelectionList(
4131 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4132
4133 auto node = SelList.begin();
4134 while (node != SelList.end()) {
4135 SelectItem *pFindSel = *node;
4136
4137 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4138
4139 if (pt && pt->IsVisible()) {
4140 m_pRolloverTrackSeg = pFindSel;
4141 showTrackRollover = true;
4142
4143 if (NULL == m_pTrackRolloverWin) {
4144 m_pTrackRolloverWin = new RolloverWin(this, 10);
4145 m_pTrackRolloverWin->IsActive(false);
4146 }
4147
4148 if (!m_pTrackRolloverWin->IsActive()) {
4149 wxString s;
4150 TrackPoint *segShow_point_a =
4151 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4152 TrackPoint *segShow_point_b =
4153 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4154
4155 double brg, dist;
4156 DistanceBearingMercator(
4157 segShow_point_b->m_lat, segShow_point_b->m_lon,
4158 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4159
4160 if (!pt->m_bIsInLayer)
4161 s.Append(_("Track") + ": ");
4162 else
4163 s.Append(_("Layer Track: "));
4164
4165 if (pt->GetName().IsEmpty())
4166 s.Append(_("(unnamed)"));
4167 else
4168 s.Append(pt->GetName());
4169 double tlenght = pt->Length();
4170 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4171 if (pt->GetLastPoint()->GetTimeString() &&
4172 pt->GetPoint(0)->GetTimeString()) {
4173 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4174 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4175 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4176 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4177 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4178 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4179 << getUsrSpeedUnit();
4180 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4181 : ttime.Format(" %H:%M"));
4182 }
4183 }
4184
4185 if (g_bShowTrackPointTime &&
4186 strlen(segShow_point_b->GetTimeString())) {
4187 wxString stamp = segShow_point_b->GetTimeString();
4188 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4189 if (timestamp.IsValid()) {
4190 // Format track rollover timestamp to OCPN global TZ setting
4193 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4194 }
4195 s << "\n" << _("Segment Created: ") << stamp;
4196 }
4197
4198 s << "\n";
4199 if (g_bShowTrue)
4200 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4201 0x00B0);
4202
4203 if (g_bShowMag) {
4204 double latAverage =
4205 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4206 double lonAverage =
4207 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4208 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4209
4210 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4211 0x00B0);
4212 }
4213
4214 s << FormatDistanceAdaptive(dist);
4215
4216 if (segShow_point_a->GetTimeString() &&
4217 segShow_point_b->GetTimeString()) {
4218 wxDateTime apoint = segShow_point_a->GetCreateTime();
4219 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4220 if (apoint.IsValid() && bpoint.IsValid()) {
4221 double segmentSpeed = toUsrSpeed(
4222 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4223 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4224 << getUsrSpeedUnit();
4225 }
4226 }
4227
4228 m_pTrackRolloverWin->SetString(s);
4229
4230 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4231 LEG_ROLLOVER, win_size);
4232 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4233 m_pTrackRolloverWin->IsActive(true);
4234 b_need_refresh = true;
4235 showTrackRollover = true;
4236 break;
4237 }
4238 } else {
4239 ++node;
4240 }
4241 }
4242 } else {
4243 // Is the cursor still in select radius, and not timed out?
4244 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4245 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4246 m_pRolloverTrackSeg))
4247 showTrackRollover = false;
4248 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4249 showTrackRollover = false;
4250 else
4251 showTrackRollover = true;
4252 }
4253
4254 // Similar for AIS target rollover window
4255 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4256 showTrackRollover = false;
4257
4258 // Similar for route rollover window
4259 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4260 showTrackRollover = false;
4261
4262 // TODO We onlt show tracks on primary canvas....
4263 // if(!IsPrimaryCanvas())
4264 // showTrackRollover = false;
4265
4266 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4267 !showTrackRollover) {
4268 m_pTrackRolloverWin->IsActive(false);
4269 m_pRolloverTrackSeg = NULL;
4270 m_pTrackRolloverWin->Destroy();
4271 m_pTrackRolloverWin = NULL;
4272 b_need_refresh = true;
4273 } else if (m_pTrackRolloverWin && showTrackRollover) {
4274 m_pTrackRolloverWin->IsActive(true);
4275 b_need_refresh = true;
4276 }
4277
4278 if (b_need_refresh) Refresh();
4279}
4280
4281void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4282 if ((GetShowENCLights() || m_bsectors_shown) &&
4283 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4284 extendedSectorLegs)) {
4285 if (!m_bsectors_shown) {
4286 ReloadVP(false);
4287 m_bsectors_shown = true;
4288 }
4289 } else {
4290 if (m_bsectors_shown) {
4291 ReloadVP(false);
4292 m_bsectors_shown = false;
4293 }
4294 }
4295
4296// This is here because GTK status window update is expensive..
4297// cairo using pango rebuilds the font every time so is very
4298// inefficient
4299// Anyway, only update the status bar when this timer expires
4300#if defined(__WXGTK__) || defined(__WXQT__)
4301 {
4302 // Check the absolute range of the cursor position
4303 // There could be a window wherein the chart geoereferencing is not
4304 // valid....
4305 double cursor_lat, cursor_lon;
4306 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4307
4308 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4309 while (cursor_lon < -180.) cursor_lon += 360.;
4310
4311 while (cursor_lon > 180.) cursor_lon -= 360.;
4312
4313 SetCursorStatus(cursor_lat, cursor_lon);
4314 }
4315 }
4316#endif
4317}
4318
4319void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4320 if (!parent_frame->m_pStatusBar) return;
4321
4322 wxString s1;
4323 s1 += " ";
4324 s1 += toSDMM(1, cursor_lat);
4325 s1 += " ";
4326 s1 += toSDMM(2, cursor_lon);
4327
4328 if (STAT_FIELD_CURSOR_LL >= 0)
4329 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4330
4331 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4332
4333 double brg, dist;
4334 wxString sm;
4335 wxString st;
4336 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4337 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4338 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4339
4340 wxString s = st + sm;
4341 s << FormatDistanceAdaptive(dist);
4342
4343 // CUSTOMIZATION - LIVE ETA OPTION
4344 // -------------------------------------------------------
4345 // Calculate an "live" ETA based on route starting from the current
4346 // position of the boat and goes to the cursor of the mouse.
4347 // In any case, an standard ETA will be calculated with a default speed
4348 // of the boat to give an estimation of the route (in particular if GPS
4349 // is off).
4350
4351 // Display only if option "live ETA" is selected in Settings > Display >
4352 // General.
4353 if (g_bShowLiveETA) {
4354 float realTimeETA;
4355 float boatSpeed;
4356 float boatSpeedDefault = g_defaultBoatSpeed;
4357
4358 // Calculate Estimate Time to Arrival (ETA) in minutes
4359 // Check before is value not closed to zero (it will make an very big
4360 // number...)
4361 if (!std::isnan(gSog)) {
4362 boatSpeed = gSog;
4363 if (boatSpeed < 0.5) {
4364 realTimeETA = 0;
4365 } else {
4366 realTimeETA = dist / boatSpeed * 60;
4367 }
4368 } else {
4369 realTimeETA = 0;
4370 }
4371
4372 // Add space after distance display
4373 s << " ";
4374 // Display ETA
4375 s << minutesToHoursDays(realTimeETA);
4376
4377 // In any case, display also an ETA with default speed at 6knts
4378
4379 s << " [@";
4380 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4381 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4382 s << " ";
4383 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4384 s << "]";
4385 }
4386 // END OF - LIVE ETA OPTION
4387
4388 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4389}
4390
4391// CUSTOMIZATION - FORMAT MINUTES
4392// -------------------------------------------------------
4393// New function to format minutes into a more readable format:
4394// * Hours + minutes, or
4395// * Days + hours.
4396wxString minutesToHoursDays(float timeInMinutes) {
4397 wxString s;
4398
4399 if (timeInMinutes == 0) {
4400 s << "--min";
4401 }
4402
4403 // Less than 60min, keep time in minutes
4404 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4405 s << wxString::Format("%d", (int)timeInMinutes);
4406 s << "min";
4407 }
4408
4409 // Between 1h and less than 24h, display time in hours, minutes
4410 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4411 int hours;
4412 int min;
4413 hours = (int)timeInMinutes / 60;
4414 min = (int)timeInMinutes % 60;
4415
4416 if (min == 0) {
4417 s << wxString::Format("%d", hours);
4418 s << "h";
4419 } else {
4420 s << wxString::Format("%d", hours);
4421 s << "h";
4422 s << wxString::Format("%d", min);
4423 s << "min";
4424 }
4425
4426 }
4427
4428 // More than 24h, display time in days, hours
4429 else if (timeInMinutes > 24 * 60) {
4430 int days;
4431 int hours;
4432 days = (int)(timeInMinutes / 60) / 24;
4433 hours = (int)(timeInMinutes / 60) % 24;
4434
4435 if (hours == 0) {
4436 s << wxString::Format("%d", days);
4437 s << "d";
4438 } else {
4439 s << wxString::Format("%d", days);
4440 s << "d";
4441 s << wxString::Format("%d", hours);
4442 s << "h";
4443 }
4444 }
4445
4446 return s;
4447}
4448
4449// END OF CUSTOMIZATION - FORMAT MINUTES
4450// Thanks open source code ;-)
4451// -------------------------------------------------------
4452
4453void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4454 double clat, clon;
4455 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4456 *lat = clat;
4457 *lon = clon;
4458}
4459
4460void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4461 wxPoint2DDouble *r) {
4462 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4463}
4464
4466 double rlon, wxPoint2DDouble *r) {
4467 // If the Current Chart is a raster chart, and the
4468 // requested lat/long is within the boundaries of the chart,
4469 // and the VP is not rotated,
4470 // then use the embedded BSB chart georeferencing algorithm
4471 // for greater accuracy
4472 // Additionally, use chart embedded georef if the projection is TMERC
4473 // i.e. NOT MERCATOR and NOT POLYCONIC
4474
4475 // If for some reason the chart rejects the request by returning an error,
4476 // then fall back to Viewport Projection estimate from canvas parameters
4477 if (!g_bopengl && m_singleChart &&
4478 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4479 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4480 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4481 (m_singleChart->GetChartProjectionType() !=
4482 PROJECTION_TRANSVERSE_MERCATOR) &&
4483 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4484 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4485 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4486 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4487 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4488 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4489 // Cur_BSB_Ch->GetCOVRTablenPoints
4490 // ( 0 ), rlon,
4491 // rlat );
4492 // bInside = true;
4493 // if ( bInside )
4494 if (Cur_BSB_Ch) {
4495 // This is a Raster chart....
4496 // If the VP is changing, the raster chart parameters may not yet be
4497 // setup So do that before accessing the chart's embedded
4498 // georeferencing
4499 Cur_BSB_Ch->SetVPRasterParms(vp);
4500 double rpixxd, rpixyd;
4501 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4502 r->m_x = rpixxd;
4503 r->m_y = rpixyd;
4504 return;
4505 }
4506 }
4507 }
4508
4509 // if needed, use the VPoint scaling estimator,
4510 *r = vp.GetDoublePixFromLL(rlat, rlon);
4511}
4512
4513// This routine might be deleted and all of the rendering improved
4514// to have floating point accuracy
4515bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4516 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4517}
4518
4519bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4520 wxPoint *r) {
4521 wxPoint2DDouble p;
4522 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4523
4524 // some projections give nan values when invisible values (other side of
4525 // world) are requested we should stop using integer coordinates or return
4526 // false here (and test it everywhere)
4527 if (std::isnan(p.m_x)) {
4528 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4529 return false;
4530 }
4531
4532 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4533 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4534 else
4535 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4536
4537 return true;
4538}
4539
4540void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4541 double &lon) {
4542 // If the Current Chart is a raster chart, and the
4543 // requested x,y is within the boundaries of the chart,
4544 // and the VP is not rotated,
4545 // then use the embedded BSB chart georeferencing algorithm
4546 // for greater accuracy
4547 // Additionally, use chart embedded georef if the projection is TMERC
4548 // i.e. NOT MERCATOR and NOT POLYCONIC
4549
4550 // If for some reason the chart rejects the request by returning an error,
4551 // then fall back to Viewport Projection estimate from canvas parameters
4552 bool bUseVP = true;
4553
4554 if (!g_bopengl && m_singleChart &&
4555 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4556 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4557 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4558 (m_singleChart->GetChartProjectionType() !=
4559 PROJECTION_TRANSVERSE_MERCATOR) &&
4560 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4561 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4562 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4563 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4564
4565 // TODO maybe need iterative process to validate bInside
4566 // first pass is mercator, then check chart boundaries
4567
4568 if (Cur_BSB_Ch) {
4569 // This is a Raster chart....
4570 // If the VP is changing, the raster chart parameters may not yet be
4571 // setup So do that before accessing the chart's embedded
4572 // georeferencing
4573 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4574
4575 double slat, slon;
4576 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4577 lat = slat;
4578
4579 if (slon < -180.)
4580 slon += 360.;
4581 else if (slon > 180.)
4582 slon -= 360.;
4583
4584 lon = slon;
4585 bUseVP = false;
4586 }
4587 }
4588 }
4589
4590 // if needed, use the VPoint scaling estimator
4591 if (bUseVP) {
4592 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4593 }
4594}
4595
4597 StopMovement();
4598 DoZoomCanvas(factor, false);
4599 extendedSectorLegs.clear();
4600}
4601
4602void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4603 bool stoptimer) {
4604 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4605
4606 if (g_bsmoothpanzoom) {
4607 if (StartTimedMovement(stoptimer)) {
4608 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4609 m_zoom_factor = factor;
4610 }
4611
4612 m_zoom_target = VPoint.chart_scale / factor;
4613 } else {
4614 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4615
4616 DoZoomCanvas(factor, can_zoom_to_cursor);
4617 }
4618
4619 extendedSectorLegs.clear();
4620}
4621
4622void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4623 // possible on startup
4624 if (!ChartData) return;
4625 if (!m_pCurrentStack) return;
4626
4627 /* TODO: queue the quilted loading code to a background thread
4628 so yield is never called from here, and also rendering is not delayed */
4629
4630 // Cannot allow Yield() re-entrancy here
4631 if (m_bzooming) return;
4632 m_bzooming = true;
4633
4634 double old_ppm = GetVP().view_scale_ppm;
4635
4636 // Capture current cursor position for zoom to cursor
4637 double zlat = m_cursor_lat;
4638 double zlon = m_cursor_lon;
4639
4640 double proposed_scale_onscreen =
4641 GetVP().chart_scale /
4642 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4643 bool b_do_zoom = false;
4644
4645 if (factor > 1) {
4646 b_do_zoom = true;
4647
4648 // double zoom_factor = factor;
4649
4650 ChartBase *pc = NULL;
4651
4652 if (!VPoint.b_quilt) {
4653 pc = m_singleChart;
4654 } else {
4655 if (!m_disable_adjust_on_zoom) {
4656 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4657 if (new_db_index >= 0)
4658 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4659 else { // for whatever reason, no reference chart is known
4660 // Choose the smallest scale chart on the current stack
4661 // and then adjust for scale range
4662 int current_ref_stack_index = -1;
4663 if (m_pCurrentStack->nEntry) {
4664 int trial_index =
4665 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4666 m_pQuilt->SetReferenceChart(trial_index);
4667 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4668 if (new_db_index >= 0)
4669 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4670 }
4671 }
4672
4673 if (m_pCurrentStack)
4674 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4675 new_db_index); // highlite the correct bar entry
4676 }
4677 }
4678
4679 if (pc) {
4680 // double target_scale_ppm = GetVPScale() * zoom_factor;
4681 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4682 // target_scale_ppm;
4683
4684 // Query the chart to determine the appropriate zoom range
4685 double min_allowed_scale =
4686 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4687
4688 if (proposed_scale_onscreen < min_allowed_scale) {
4689 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4690 m_zoom_factor = 1; /* stop zooming */
4691 b_do_zoom = false;
4692 } else
4693 proposed_scale_onscreen = min_allowed_scale;
4694 }
4695
4696 } else {
4697 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4698 }
4699
4700 } else if (factor < 1) {
4701 b_do_zoom = true;
4702
4703 ChartBase *pc = NULL;
4704
4705 bool b_smallest = false;
4706
4707 if (!VPoint.b_quilt) { // not quilted
4708 pc = m_singleChart;
4709
4710 if (pc) {
4711 // If m_singleChart is not on the screen, unbound the zoomout
4712 LLBBox viewbox = VPoint.GetBBox();
4713 // BoundingBox chart_box;
4714 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4715 double max_allowed_scale;
4716
4717 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4718
4719 // We can allow essentially unbounded zoomout in single chart mode
4720 // if( ChartData->GetDBBoundingBox( current_index,
4721 // &chart_box ) &&
4722 // !viewbox.IntersectOut( chart_box ) )
4723 // // Clamp the minimum scale zoom-out to the value
4724 // specified by the chart max_allowed_scale =
4725 // wxMin(max_allowed_scale, 4.0 *
4726 // pc->GetNormalScaleMax(
4727 // GetCanvasScaleFactor(),
4728 // GetCanvasWidth() ) );
4729 if (proposed_scale_onscreen > max_allowed_scale) {
4730 m_zoom_factor = 1; /* stop zooming */
4731 proposed_scale_onscreen = max_allowed_scale;
4732 }
4733 }
4734
4735 } else {
4736 if (!m_disable_adjust_on_zoom) {
4737 int new_db_index =
4738 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4739 if (new_db_index >= 0)
4740 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4741
4742 if (m_pCurrentStack)
4743 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4744 new_db_index); // highlite the correct bar entry
4745
4746 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4747
4748 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4749 proposed_scale_onscreen =
4750 wxMin(proposed_scale_onscreen,
4751 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4752 }
4753
4754 // set a minimum scale
4755 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4756 m_absolute_min_scale_ppm)
4757 proposed_scale_onscreen =
4758 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4759 }
4760 }
4761 double new_scale =
4762 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4763
4764 if (b_do_zoom) {
4765 // Disable ZTC if lookahead is ON, and currently b_follow is active
4766 bool b_allow_ztc = true;
4767 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4768 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4769 if (m_bLookAhead) {
4770 double brg, distance;
4771 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4772 &distance);
4773 dir_to_shift = brg;
4774 meters_to_shift = distance * 1852;
4775 }
4776 // Arrange to combine the zoom and pan into one operation for smoother
4777 // appearance
4778 SetVPScale(new_scale, false); // adjust, but deferred refresh
4779 wxPoint r;
4780 GetCanvasPointPix(zlat, zlon, &r);
4781 // this will emit the Refresh()
4782 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4783 } else {
4784 SetVPScale(new_scale);
4785 if (m_bFollow) DoCanvasUpdate();
4786 }
4787 }
4788
4789 m_bzooming = false;
4790}
4791
4792void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4793 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4794 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4795}
4796
4797int rot;
4798void ChartCanvas::RotateCanvas(double dir) {
4799 // SetUpMode(NORTH_UP_MODE);
4800
4801 if (g_bsmoothpanzoom) {
4802 if (StartTimedMovement()) {
4803 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4804 m_rotation_speed = dir * 60;
4805 }
4806 } else {
4807 double speed = dir * 10;
4808 if (m_modkeys == wxMOD_ALT) speed /= 20;
4809 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4810 }
4811}
4812
4813void ChartCanvas::DoRotateCanvas(double rotation) {
4814 while (rotation < 0) rotation += 2 * PI;
4815 while (rotation > 2 * PI) rotation -= 2 * PI;
4816
4817 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4818
4819 SetVPRotation(rotation);
4820 parent_frame->UpdateRotationState(VPoint.rotation);
4821}
4822
4823void ChartCanvas::DoTiltCanvas(double tilt) {
4824 while (tilt < 0) tilt = 0;
4825 while (tilt > .95) tilt = .95;
4826
4827 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4828
4829 VPoint.tilt = tilt;
4830 Refresh(false);
4831}
4832
4833void ChartCanvas::TogglebFollow() {
4834 if (!m_bFollow)
4835 SetbFollow();
4836 else
4837 ClearbFollow();
4838}
4839
4840void ChartCanvas::ClearbFollow() {
4841 m_bFollow = false; // update the follow flag
4842
4843 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4844
4845 UpdateFollowButtonState();
4846
4847 DoCanvasUpdate();
4848 ReloadVP();
4849 parent_frame->SetChartUpdatePeriod();
4850}
4851
4852void ChartCanvas::SetbFollow() {
4853 // Is the OWNSHIP on-screen?
4854 // If not, then reset the OWNSHIP offset to 0 (center screen)
4855 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4856 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4857 m_OSoffsetx = 0;
4858 m_OSoffsety = 0;
4859 }
4860
4861 // Apply the present b_follow offset values to ship position
4862 wxPoint2DDouble p;
4864 p.m_x += m_OSoffsetx;
4865 p.m_y -= m_OSoffsety;
4866
4867 // compute the target center screen lat/lon
4868 double dlat, dlon;
4869 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4870
4871 JumpToPosition(dlat, dlon, GetVPScale());
4872 m_bFollow = true;
4873
4874 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4875 UpdateFollowButtonState();
4876
4877 if (!g_bSmoothRecenter) {
4878 DoCanvasUpdate();
4879 ReloadVP();
4880 }
4881 parent_frame->SetChartUpdatePeriod();
4882}
4883
4884void ChartCanvas::UpdateFollowButtonState() {
4885 if (m_muiBar) {
4886 if (!m_bFollow)
4887 m_muiBar->SetFollowButtonState(0);
4888 else {
4889 if (m_bLookAhead)
4890 m_muiBar->SetFollowButtonState(2);
4891 else
4892 m_muiBar->SetFollowButtonState(1);
4893 }
4894 }
4895
4896#ifdef __ANDROID__
4897 if (!m_bFollow)
4898 androidSetFollowTool(0);
4899 else {
4900 if (m_bLookAhead)
4901 androidSetFollowTool(2);
4902 else
4903 androidSetFollowTool(1);
4904 }
4905#endif
4906
4907 // Look for plugin using API-121 or later
4908 // If found, make the follow state callback.
4909 if (g_pi_manager) {
4910 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4911 if (pic->m_enabled && pic->m_init_state) {
4912 switch (pic->m_api_version) {
4913 case 121: {
4914 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4915 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4916 break;
4917 }
4918 default:
4919 break;
4920 }
4921 }
4922 }
4923 }
4924}
4925
4926void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4927 if (g_bSmoothRecenter && !m_routeState) {
4928 if (StartSmoothJump(lat, lon, scale_ppm))
4929 return;
4930 else {
4931 // move closer to the target destination, and try again
4932 double gcDist, gcBearingEnd;
4933 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4934 &gcBearingEnd);
4935 gcBearingEnd += 180;
4936 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4937 GetCanvasWidth() / GetVPScale(); // meters
4938 double lon_offset =
4939 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4940 double new_lat = lat + (lat_offset / (1852 * 60));
4941 double new_lon = lon + (lon_offset / (1852 * 60));
4942 SetViewPoint(new_lat, new_lon);
4943 ReloadVP();
4944 StartSmoothJump(lat, lon, scale_ppm);
4945 return;
4946 }
4947 }
4948
4949 if (lon > 180.0) lon -= 360.0;
4950 m_vLat = lat;
4951 m_vLon = lon;
4952 StopMovement();
4953 m_bFollow = false;
4954
4955 if (!GetQuiltMode()) {
4956 double skew = 0;
4957 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4958 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4959 } else {
4960 if (scale_ppm != GetVPScale()) {
4961 // XXX should be done in SetViewPoint
4962 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4963 AdjustQuiltRefChart();
4964 }
4965 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4966 }
4967
4968 ReloadVP();
4969
4970 UpdateFollowButtonState();
4971
4972 // TODO
4973 // if( g_pi_manager ) {
4974 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4975 // }
4976}
4977
4978bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
4979 // Check distance to jump, in pixels at current chart scale
4980 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
4981 // width.
4982 double gcDist;
4983 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
4984 double distance_pixels = gcDist * GetVPScale();
4985 if (distance_pixels > 0.5 * GetCanvasWidth()) {
4986 // Jump is too far, try again
4987 return false;
4988 }
4989
4990 // Save where we're coming from
4991 m_startLat = m_vLat;
4992 m_startLon = m_vLon;
4993 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
4994
4995 // Save where we want to end up
4996 m_endLat = lat;
4997 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
4998 m_endScale = scale_ppm;
4999
5000 // Setup timing
5001 m_animationDuration = 600; // ms
5002 m_animationStart = wxGetLocalTimeMillis();
5003
5004 // Stop any previous movement, ensure no conflicts
5005 StopMovement();
5006 m_bFollow = false;
5007
5008 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5009 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5010 m_animationActive = true;
5011
5012 return true;
5013}
5014
5015void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5016 // Calculate time fraction from 0..1
5017 wxLongLong now = wxGetLocalTimeMillis();
5018 double elapsed = (now - m_animationStart).ToDouble();
5019 double t = elapsed / m_animationDuration.ToDouble();
5020 if (t > 1.0) t = 1.0;
5021
5022 // Ease function for smoother movement
5023 double e = easeOutCubic(t);
5024
5025 // Interpolate lat/lon/scale
5026 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5027 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5028 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5029
5030 // Update viewpoint
5031 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5032 // portion)
5033 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5034 ReloadVP();
5035
5036 // If we reached the end, stop the timer and finalize
5037 if (t >= 1.0) {
5038 m_easeTimer.Stop();
5039 m_animationActive = false;
5040 UpdateFollowButtonState();
5041 ZoomCanvasSimple(1.0001);
5042 DoCanvasUpdate();
5043 ReloadVP();
5044 }
5045}
5046
5047bool ChartCanvas::PanCanvas(double dx, double dy) {
5048 if (!ChartData) return false;
5049 extendedSectorLegs.clear();
5050
5051 double dlat, dlon;
5052 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5053
5054 int iters = 0;
5055 for (;;) {
5056 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5057
5058 if (iters++ > 5) return false;
5059 if (!std::isnan(dlat)) break;
5060
5061 dx *= .5, dy *= .5;
5062 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5063 }
5064
5065 // avoid overshooting the poles
5066 if (dlat > 90)
5067 dlat = 90;
5068 else if (dlat < -90)
5069 dlat = -90;
5070
5071 if (dlon > 360.) dlon -= 360.;
5072 if (dlon < -360.) dlon += 360.;
5073
5074 // This should not really be necessary, but round-trip georef on some
5075 // charts is not perfect, So we can get creep on repeated unidimensional
5076 // pans, and corrupt chart cacheing.......
5077
5078 // But this only works on north-up projections
5079 // TODO: can we remove this now?
5080 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5081 // .001 ) ) {
5082 //
5083 // if( dx == 0 ) dlon = clon;
5084 // if( dy == 0 ) dlat = clat;
5085 // }
5086
5087 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5088
5089 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5090
5091 if (VPoint.b_quilt) {
5092 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5093 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5094 // Tweak the scale slightly for a new ref chart
5095 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5096 if (pc) {
5097 double tweak_scale_ppm =
5098 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5099 SetVPScale(tweak_scale_ppm);
5100 }
5101 }
5102
5103 if (new_ref_dbIndex == -1) {
5104#pragma GCC diagnostic push
5105#pragma GCC diagnostic ignored "-Warray-bounds"
5106 // The compiler sees a -1 index being used. Does not happen, though.
5107
5108 // for whatever reason, no reference chart is known
5109 // Probably panned out of the coverage region
5110 // If any charts are anywhere on-screen, choose the smallest
5111 // scale chart on the screen to be a new reference chart.
5112 int trial_index = -1;
5113 if (m_pCurrentStack->nEntry) {
5114 int trial_index =
5115 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5116 }
5117
5118 if (trial_index < 0) {
5119 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5120 if (full_screen_array.size())
5121 trial_index = full_screen_array[full_screen_array.size() - 1];
5122 }
5123
5124 if (trial_index >= 0) {
5125 m_pQuilt->SetReferenceChart(trial_index);
5126 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5127 VPoint.rotation);
5128 ReloadVP();
5129 }
5130#pragma GCC diagnostic pop
5131 }
5132 }
5133
5134 // Turn off bFollow only if the ownship has left the screen
5135 if (m_bFollow) {
5136 double offx, offy;
5137 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5138
5139 double offset_angle = atan2(offy, offx);
5140 double offset_distance = sqrt((offy * offy) + (offx * offx));
5141 double chart_angle = GetVPRotation();
5142 double target_angle = chart_angle - offset_angle;
5143 double d_east_mod = offset_distance * cos(target_angle);
5144 double d_north_mod = offset_distance * sin(target_angle);
5145
5146 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5147 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5148
5149 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5150 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5151 m_bFollow = false; // update the follow flag
5152 UpdateFollowButtonState();
5153 }
5154 }
5155
5156 Refresh(false);
5157
5158 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5159
5160 return true;
5161}
5162
5163bool ChartCanvas::IsOwnshipOnScreen() {
5164 wxPoint r;
5166 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5167 ((r.y > 0) && r.y < GetCanvasHeight()))
5168 return true;
5169 else
5170 return false;
5171}
5172
5173void ChartCanvas::ReloadVP(bool b_adjust) {
5174 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5175
5176 LoadVP(VPoint, b_adjust);
5177}
5178
5179void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5180#ifdef ocpnUSE_GL
5181 if (g_bopengl && m_glcc) {
5182 m_glcc->Invalidate();
5183 if (m_glcc->GetSize() != GetSize()) {
5184 m_glcc->SetSize(GetSize());
5185 }
5186 } else
5187#endif
5188 {
5189 m_cache_vp.Invalidate();
5190 m_bm_cache_vp.Invalidate();
5191 }
5192
5193 VPoint.Invalidate();
5194
5195 if (m_pQuilt) m_pQuilt->Invalidate();
5196
5197 // Make sure that the Selected Group is sensible...
5198 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5199 // m_groupIndex = 0;
5200 // if( !CheckGroup( m_groupIndex ) )
5201 // m_groupIndex = 0;
5202
5203 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5204 vp.m_projection_type, b_adjust);
5205}
5206
5207void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5208 m_pQuilt->SetReferenceChart(dbIndex);
5209 VPoint.Invalidate();
5210 m_pQuilt->Invalidate();
5211}
5212
5213double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5214 if (m_pQuilt)
5215 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5216 else
5217 return vp.view_scale_ppm;
5218}
5219
5220// Verify and adjust the current reference chart,
5221// so that it will not lead to excessive overzoom or underzoom onscreen
5222int ChartCanvas::AdjustQuiltRefChart() {
5223 int ret = -1;
5224 if (m_pQuilt) {
5225 wxASSERT(ChartData);
5226 ChartBase *pc =
5227 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5228 if (pc) {
5229 double min_ref_scale =
5230 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5231 double max_ref_scale =
5232 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5233
5234 if (VPoint.chart_scale < min_ref_scale) {
5235 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5236 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5237 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5238 } else {
5239 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5240
5241 if (!brender_ok) {
5242 int target_stack_index = wxNOT_FOUND;
5243 int il = 0;
5244 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5245 if (index == m_pQuilt->GetRefChartdbIndex()) {
5246 target_stack_index = il;
5247 break;
5248 }
5249 il++;
5250 }
5251 if (wxNOT_FOUND == target_stack_index) // should never happen...
5252 target_stack_index = 0;
5253
5254 int ref_family = pc->GetChartFamily();
5255 int extended_array_count =
5256 m_pQuilt->GetExtendedStackIndexArray().size();
5257 while ((!brender_ok) &&
5258 ((int)target_stack_index < (extended_array_count - 1))) {
5259 target_stack_index++;
5260 int test_db_index =
5261 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5262
5263 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5264 IsChartQuiltableRef(test_db_index)) {
5265 // open the target, and check the min_scale
5266 ChartBase *ptest_chart =
5267 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5268 if (ptest_chart) {
5269 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5270 }
5271 }
5272 }
5273
5274 if (brender_ok) { // found a better reference chart
5275 int new_db_index =
5276 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5277 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5278 IsChartQuiltableRef(new_db_index)) {
5279 m_pQuilt->SetReferenceChart(new_db_index);
5280 ret = new_db_index;
5281 } else
5282 ret = m_pQuilt->GetRefChartdbIndex();
5283 } else
5284 ret = m_pQuilt->GetRefChartdbIndex();
5285
5286 } else
5287 ret = m_pQuilt->GetRefChartdbIndex();
5288 }
5289 } else
5290 ret = -1;
5291 }
5292
5293 return ret;
5294}
5295
5296void ChartCanvas::UpdateCanvasOnGroupChange() {
5297 delete m_pCurrentStack;
5298 m_pCurrentStack = new ChartStack;
5299 wxASSERT(ChartData);
5300 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5301 m_groupIndex);
5302
5303 if (m_pQuilt) {
5304 m_pQuilt->Compose(VPoint);
5305 SetFocus();
5306 }
5307}
5308
5309bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5310 double latNE, double lonNE) {
5311 // Center Point
5312 double latc = (latSW + latNE) / 2.0;
5313 double lonc = (lonSW + lonNE) / 2.0;
5314
5315 // Get scale in ppm (latitude)
5316 double ne_easting, ne_northing;
5317 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5318
5319 double sw_easting, sw_northing;
5320 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5321
5322 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5323
5324 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5325}
5326
5327bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5328 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5329 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5330}
5331
5332bool ChartCanvas::SetVPProjection(int projection) {
5333 if (!g_bopengl) // alternative projections require opengl
5334 return false;
5335
5336 // the view scale varies depending on geographic location and projection
5337 // rescale to keep the relative scale on the screen the same
5338 double prev_true_scale_ppm = m_true_scale_ppm;
5339 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5340 VPoint.skew, VPoint.rotation, projection) &&
5341 SetVPScale(wxMax(
5342 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5343 m_absolute_min_scale_ppm));
5344}
5345
5346bool ChartCanvas::SetViewPoint(double lat, double lon) {
5347 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5348 VPoint.rotation);
5349}
5350
5351bool ChartCanvas::SetVPRotation(double angle) {
5352 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5353 VPoint.skew, angle);
5354}
5355bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5356 double skew, double rotation, int projection,
5357 bool b_adjust, bool b_refresh) {
5358 bool b_ret = false;
5359 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5360 skew -= 2 * PI;
5361 // Any sensible change?
5362 if (VPoint.IsValid()) {
5363 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5364 (fabs(VPoint.skew - skew) < 1e-9) &&
5365 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5366 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5367 (VPoint.m_projection_type == projection ||
5368 projection == PROJECTION_UNKNOWN))
5369 return false;
5370 }
5371 if (VPoint.m_projection_type != projection)
5372 VPoint.InvalidateTransformCache(); // invalidate
5373
5374 // Take a local copy of the last viewport
5375 ViewPort last_vp = VPoint;
5376
5377 VPoint.skew = skew;
5378 VPoint.clat = lat;
5379 VPoint.clon = lon;
5380 VPoint.rotation = rotation;
5381 VPoint.view_scale_ppm = scale_ppm;
5382 if (projection != PROJECTION_UNKNOWN)
5383 VPoint.SetProjectionType(projection);
5384 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5385 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5386
5387 // don't allow latitude above 88 for mercator (90 is infinity)
5388 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5389 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5390 if (VPoint.clat > 89.5)
5391 VPoint.clat = 89.5;
5392 else if (VPoint.clat < -89.5)
5393 VPoint.clat = -89.5;
5394 }
5395
5396 // don't zoom out too far for transverse mercator polyconic until we resolve
5397 // issues
5398 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5399 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5400 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5401
5402 // SetVPRotation(rotation);
5403
5404 if (!g_bopengl) // tilt is not possible without opengl
5405 VPoint.tilt = 0;
5406
5407 if ((VPoint.pix_width <= 0) ||
5408 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5409 return false;
5410
5411 bool bwasValid = VPoint.IsValid();
5412 VPoint.Validate(); // Mark this ViewPoint as OK
5413
5414 // Has the Viewport scale changed? If so, invalidate the vp
5415 if (last_vp.view_scale_ppm != scale_ppm) {
5416 m_cache_vp.Invalidate();
5417 InvalidateGL();
5418 }
5419
5420 // A preliminary value, may be tweaked below
5421 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5422
5423 // recompute cursor position
5424 // and send to interested plugins if the mouse is actually in this window
5425 int mouseX = mouse_x;
5426 int mouseY = mouse_y;
5427 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5428 (mouseY < VPoint.pix_height)) {
5429 double lat, lon;
5430 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5431 m_cursor_lat = lat;
5432 m_cursor_lon = lon;
5433 SendCursorLatLonToAllPlugIns(lat, lon);
5434 }
5435
5436 if (!VPoint.b_quilt && m_singleChart) {
5437 VPoint.SetBoxes();
5438
5439 // Allow the chart to adjust the new ViewPort for performance optimization
5440 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5441 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5442
5443 // If there is a sensible change in the chart render, refresh the whole
5444 // screen
5445 if ((!m_cache_vp.IsValid()) ||
5446 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5447 Refresh(false);
5448 b_ret = true;
5449 } else {
5450 wxPoint cp_last, cp_this;
5451 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5452 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5453
5454 if (cp_last != cp_this) {
5455 Refresh(false);
5456 b_ret = true;
5457 }
5458 }
5459 // Create the stack
5460 if (m_pCurrentStack) {
5461 assert(ChartData != 0);
5462 int current_db_index;
5463 current_db_index =
5464 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5465
5466 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5467 m_groupIndex);
5468 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5469 }
5470
5471 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5472 }
5473
5474 // Handle the quilted case
5475 if (VPoint.b_quilt) {
5476 VPoint.SetBoxes();
5477
5478 if (last_vp.view_scale_ppm != scale_ppm)
5479 m_pQuilt->InvalidateAllQuiltPatchs();
5480
5481 // Create the quilt
5482 if (ChartData /*&& ChartData->IsValid()*/) {
5483 if (!m_pCurrentStack) return false;
5484
5485 int current_db_index;
5486 current_db_index =
5487 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5488
5489 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5490 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5491
5492 // Check to see if the current quilt reference chart is in the new stack
5493 int current_ref_stack_index = -1;
5494 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5495 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5496 current_ref_stack_index = i;
5497 }
5498
5499 if (g_bFullScreenQuilt) {
5500 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5501 }
5502
5503 // We might need a new Reference Chart
5504 bool b_needNewRef = false;
5505
5506 // If the new stack does not contain the current ref chart....
5507 if ((-1 == current_ref_stack_index) &&
5508 (m_pQuilt->GetRefChartdbIndex() >= 0))
5509 b_needNewRef = true;
5510
5511 // Would the current Ref Chart be excessively underzoomed?
5512 // We need to check this here to be sure, since we cannot know where the
5513 // reference chart was assigned. For instance, the reference chart may
5514 // have been selected from the config file, or from a long jump with a
5515 // chart family switch implicit. Anyway, we check to be sure....
5516 bool renderable = true;
5517 ChartBase *referenceChart =
5518 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5519 if (referenceChart) {
5520 double chartMaxScale = referenceChart->GetNormalScaleMax(
5521 GetCanvasScaleFactor(), GetCanvasWidth());
5522 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5523 }
5524 if (!renderable) b_needNewRef = true;
5525
5526 // Need new refchart?
5527 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5528 const ChartTableEntry &cte_ref =
5529 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5530 int target_scale = cte_ref.GetScale();
5531 int target_type = cte_ref.GetChartType();
5532 int candidate_stack_index;
5533
5534 // reset the ref chart in a way that does not lead to excessive
5535 // underzoom, for performance reasons Try to find a chart that is the
5536 // same type, and has a scale of just smaller than the current ref
5537 // chart
5538
5539 candidate_stack_index = 0;
5540 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5541 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5542 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5543 int candidate_scale = cte_candidate.GetScale();
5544 int candidate_type = cte_candidate.GetChartType();
5545
5546 if ((candidate_scale >= target_scale) &&
5547 (candidate_type == target_type)) {
5548 bool renderable = true;
5549 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5550 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5551 if (tentative_referenceChart) {
5552 double chartMaxScale =
5553 tentative_referenceChart->GetNormalScaleMax(
5554 GetCanvasScaleFactor(), GetCanvasWidth());
5555 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5556 }
5557
5558 if (renderable) break;
5559 }
5560
5561 candidate_stack_index++;
5562 }
5563
5564 // If that did not work, look for a chart of just larger scale and
5565 // same type
5566 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5567 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5568 while (candidate_stack_index >= 0) {
5569 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5570 if (idx >= 0) {
5571 const ChartTableEntry &cte_candidate =
5572 ChartData->GetChartTableEntry(idx);
5573 int candidate_scale = cte_candidate.GetScale();
5574 int candidate_type = cte_candidate.GetChartType();
5575
5576 if ((candidate_scale <= target_scale) &&
5577 (candidate_type == target_type))
5578 break;
5579 }
5580 candidate_stack_index--;
5581 }
5582 }
5583
5584 // and if that did not work, chose stack entry 0
5585 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5586 (candidate_stack_index < 0))
5587 candidate_stack_index = 0;
5588
5589 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5590
5591 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5592 }
5593
5594 if (!g_bopengl) {
5595 // Preset the VPoint projection type to match what the quilt projection
5596 // type will be
5597 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5598
5599 // Always keep the default Mercator projection if the reference chart is
5600 // not in the PatchList or the scale is too small for it to render.
5601
5602 bool renderable = true;
5603 ChartBase *referenceChart =
5604 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5605 if (referenceChart) {
5606 double chartMaxScale = referenceChart->GetNormalScaleMax(
5607 GetCanvasScaleFactor(), GetCanvasWidth());
5608 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5609 proj = ChartData->GetDBChartProj(ref_db_index);
5610 } else
5611 proj = PROJECTION_MERCATOR;
5612
5613 VPoint.b_MercatorProjectionOverride =
5614 (m_pQuilt->GetnCharts() == 0 || !renderable);
5615
5616 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5617
5618 VPoint.SetProjectionType(proj);
5619 }
5620
5621 // If this quilt will be a perceptible delta from the existing quilt,
5622 // then refresh the entire screen
5623 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5624 // Allow the quilt to adjust the new ViewPort for performance
5625 // optimization This will normally be only a fractional (i.e.
5626 // sub-pixel) adjustment...
5627 if (b_adjust) {
5628 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5629 }
5630
5631 // ChartData->ClearCacheInUseFlags();
5632 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5633
5634 // wxStopWatch sw;
5635
5636#ifdef __ANDROID__
5637 // This is an optimization for panning on touch screen systems.
5638 // The quilt composition is deferred until the OnPaint() message gets
5639 // finally removed and processed from the message queue.
5640 // Takes advantage of the fact that touch-screen pan gestures are
5641 // usually short in distance,
5642 // so not requiring a full quilt rebuild until the pan gesture is
5643 // complete.
5644 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5645 // qDebug() << "Force compose";
5646 m_pQuilt->Compose(VPoint);
5647 } else {
5648 m_pQuilt->Invalidate();
5649 }
5650#else
5651 m_pQuilt->Compose(VPoint);
5652#endif
5653
5654 // printf("comp time %ld\n", sw.Time());
5655
5656 // If the extended chart stack has changed, invalidate any cached
5657 // render bitmap
5658 // if(m_pQuilt->GetXStackHash() != hash1) {
5659 // m_bm_cache_vp.Invalidate();
5660 // InvalidateGL();
5661 // }
5662
5663 ChartData->PurgeCacheUnusedCharts(0.7);
5664
5665 if (b_refresh) Refresh(false);
5666
5667 b_ret = true;
5668 }
5669 }
5670
5671 VPoint.skew = 0.; // Quilting supports 0 Skew
5672 } else if (!g_bopengl) {
5673 OcpnProjType projection = PROJECTION_UNKNOWN;
5674 if (m_singleChart) // viewport projection must match chart projection
5675 // without opengl
5676 projection = m_singleChart->GetChartProjectionType();
5677 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5678 VPoint.SetProjectionType(projection);
5679 }
5680
5681 // Has the Viewport projection changed? If so, invalidate the vp
5682 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5683 m_cache_vp.Invalidate();
5684 InvalidateGL();
5685 }
5686
5687 UpdateCanvasControlBar(); // Refresh the Piano
5688
5689 VPoint.chart_scale = 1.0; // fallback default value
5690
5691 if (VPoint.GetBBox().GetValid()) {
5692 // Update the viewpoint reference scale
5693 if (m_singleChart)
5694 VPoint.ref_scale = m_singleChart->GetNativeScale();
5695 else {
5696#ifdef __ANDROID__
5697 // This is an optimization for panning on touch screen systems.
5698 // See above.
5699 // Quilt might not be fully composed at this point, so for cm93
5700 // the reference scale may not be known.
5701 // In this case, do not update the VP ref_scale.
5702 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5703 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5704 }
5705#else
5706 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5707#endif
5708 }
5709
5710 // Calculate the on-screen displayed actual scale
5711 // by a simple traverse northward from the center point
5712 // of roughly one eighth of the canvas height
5713 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5714
5715 double delta_check =
5716 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5717 delta_check /= 8.;
5718
5719 double check_point = wxMin(89., VPoint.clat);
5720
5721 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5722
5723 double rhumbDist;
5724 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5725 VPoint.clon, 0, &rhumbDist);
5726
5727 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5728 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5729 // Calculate the distance between r1 and r in physical pixels.
5730 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5731 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5732
5733 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5734
5735 // A fall back in case of very high zoom-out, giving delta_y == 0
5736 // which can probably only happen with vector charts
5737 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5738
5739 // Another fallback, for highly zoomed out charts
5740 // This adjustment makes the displayed TrueScale correspond to the
5741 // same algorithm used to calculate the chart zoom-out limit for
5742 // ChartDummy.
5743 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5744
5745 if (m_true_scale_ppm)
5746 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5747 else
5748 VPoint.chart_scale = 1.0;
5749
5750 // Create a nice renderable string
5751 double round_factor = 1000.;
5752 if (VPoint.chart_scale <= 1000.)
5753 round_factor = 10.;
5754 else if (VPoint.chart_scale <= 10000.)
5755 round_factor = 100.;
5756 else if (VPoint.chart_scale <= 100000.)
5757 round_factor = 1000.;
5758
5759 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5760 double retina_coef = 1;
5761#ifdef ocpnUSE_GL
5762#ifdef __WXOSX__
5763 if (g_bopengl) {
5764 retina_coef = GetContentScaleFactor();
5765 }
5766#endif
5767#endif
5768
5769 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5770 // rounded to the nearest 10, 100 or 1000.
5771 //
5772 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5773 // true_scale_display. That does not make sense. The chart scale should be
5774 // the same as the true scale within the limits of the rounding factor.
5775 double true_scale_display =
5776 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5777 wxString text;
5778
5779 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5780
5781 if (m_displayed_scale_factor > 10.0)
5782 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5783 m_displayed_scale_factor);
5784 else if (m_displayed_scale_factor > 1.0)
5785 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5786 m_displayed_scale_factor);
5787 else if (m_displayed_scale_factor > 0.1) {
5788 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5789 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5790 } else if (m_displayed_scale_factor > 0.01) {
5791 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5792 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5793 } else {
5794 text.Printf(
5795 "%s %4.0f (---)", _("Scale"),
5796 true_scale_display); // Generally, no chart, so no chart scale factor
5797 }
5798
5799 m_scaleValue = true_scale_display;
5800 m_scaleText = text;
5801 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5802
5803 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5804 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5805 // Check to see if the text will fit in the StatusBar field...
5806 bool b_noshow = false;
5807 {
5808 int w = 0;
5809 int h;
5810 wxClientDC dc(parent_frame->GetStatusBar());
5811 if (dc.IsOk()) {
5812 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5813 dc.SetFont(*templateFont);
5814 dc.GetTextExtent(text, &w, &h);
5815
5816 // If text is too long for the allocated field, try to reduce the text
5817 // string a bit.
5818 wxRect rect;
5819 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5820 if (w && w > rect.width) {
5821 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5822 }
5823
5824 // Test again...if too big still, then give it up.
5825 dc.GetTextExtent(text, &w, &h);
5826
5827 if (w && w > rect.width) {
5828 b_noshow = true;
5829 }
5830 }
5831 }
5832
5833 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5834 }
5835 }
5836
5837 // Maintain member vLat/vLon
5838 m_vLat = VPoint.clat;
5839 m_vLon = VPoint.clon;
5840
5841 return b_ret;
5842}
5843
5844// Static Icon definitions for some symbols requiring
5845// scaling/rotation/translation Very specific wxDC draw commands are
5846// necessary to properly render these icons...See the code in
5847// ShipDraw()
5848
5849// This icon was adapted and scaled from the S52 Presentation Library
5850// version 3_03.
5851// Symbol VECGND02
5852
5853static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5854
5855// This ownship icon was adapted and scaled from the S52 Presentation
5856// Library version 3_03 Symbol OWNSHP05
5857static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5858 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5859
5860wxColour ChartCanvas::PredColor() {
5861 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5862 // visibility.
5863 if (SHIP_NORMAL == m_ownship_state)
5864 return GetGlobalColor("URED");
5865
5866 else if (SHIP_LOWACCURACY == m_ownship_state)
5867 return GetGlobalColor("YELO1");
5868
5869 return GetGlobalColor("NODTA");
5870}
5871
5872wxColour ChartCanvas::ShipColor() {
5873 // Establish ship color
5874 // It changes color based on GPS and Chart accuracy/availability
5875
5876 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5877
5878 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5879
5880 return GetGlobalColor("URED"); // default is OK
5881}
5882
5883void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5884 wxPoint2DDouble lShipMidPoint) {
5885 dc.SetPen(wxPen(PredColor(), 2));
5886
5887 if (SHIP_NORMAL == m_ownship_state)
5888 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5889 else
5890 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5891
5892 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5893 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5894
5895 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5896 lShipMidPoint.m_y);
5897 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5898 lShipMidPoint.m_y + 12);
5899}
5900
5901void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5902 wxPoint GPSOffsetPixels,
5903 wxPoint2DDouble lGPSPoint) {
5904 // if (m_animationActive) return;
5905 // Develop a uniform length for course predictor line dash length, based on
5906 // physical display size Use this reference length to size all other graphics
5907 // elements
5908 float ref_dim = m_display_size_mm / 24;
5909 ref_dim = wxMin(ref_dim, 12);
5910 ref_dim = wxMax(ref_dim, 6);
5911
5912 wxColour cPred;
5913 cPred.Set(g_cog_predictor_color);
5914 if (cPred == wxNullColour) cPred = PredColor();
5915
5916 // Establish some graphic element line widths dependent on the platform
5917 // display resolution
5918 // double nominal_line_width_pix = wxMax(1.0,
5919 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5920 // not less than 1 pixel
5921 double nominal_line_width_pix = wxMax(
5922 1.0,
5923 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5924
5925 // If the calculated value is greater than the config file spec value, then
5926 // use it.
5927 if (nominal_line_width_pix > g_cog_predictor_width)
5928 g_cog_predictor_width = nominal_line_width_pix;
5929
5930 // Calculate ownship Position Predictor
5931 wxPoint lPredPoint, lHeadPoint;
5932
5933 float pCog = std::isnan(gCog) ? 0 : gCog;
5934 float pSog = std::isnan(gSog) ? 0 : gSog;
5935
5936 double pred_lat, pred_lon;
5937 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5938 &pred_lat, &pred_lon);
5939 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5940
5941 // test to catch the case where COG/HDG line crosses the screen
5942 LLBBox box;
5943
5944 // Should we draw the Head vector?
5945 // Compare the points lHeadPoint and lPredPoint
5946 // If they differ by more than n pixels, and the head vector is valid, then
5947 // render the head vector
5948
5949 float ndelta_pix = 10.;
5950 double hdg_pred_lat, hdg_pred_lon;
5951 bool b_render_hdt = false;
5952 if (!std::isnan(gHdt)) {
5953 // Calculate ownship Heading pointer as a predictor
5954 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5955 &hdg_pred_lon);
5956 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5957 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5958 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5959 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5960 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5961 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5962 }
5963 }
5964
5965 // draw course over ground if they are longer than the ship
5966 wxPoint lShipMidPoint;
5967 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5968 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5969 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5970 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5971
5972 if (lpp >= img_height / 2) {
5973 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5974 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5975 !std::isnan(gSog)) {
5976 // COG Predictor
5977 float dash_length = ref_dim;
5978 wxDash dash_long[2];
5979 dash_long[0] =
5980 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
5981 g_cog_predictor_width); // Long dash , in mm <---------+
5982 dash_long[1] = dash_long[0] / 2.0; // Short gap
5983
5984 // On ultra-hi-res displays, do not allow the dashes to be greater than
5985 // 250, since it is defined as (char)
5986 if (dash_length > 250.) {
5987 dash_long[0] = 250. / g_cog_predictor_width;
5988 dash_long[1] = dash_long[0] / 2;
5989 }
5990
5991 wxPen ppPen2(cPred, g_cog_predictor_width,
5992 (wxPenStyle)g_cog_predictor_style);
5993 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
5994 ppPen2.SetDashes(2, dash_long);
5995 dc.SetPen(ppPen2);
5996 dc.StrokeLine(
5997 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
5998 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
5999
6000 if (g_cog_predictor_width > 1) {
6001 float line_width = g_cog_predictor_width / 3.;
6002
6003 wxDash dash_long3[2];
6004 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6005 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6006
6007 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6008 (wxPenStyle)g_cog_predictor_style);
6009 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6010 ppPen3.SetDashes(2, dash_long3);
6011 dc.SetPen(ppPen3);
6012 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6013 lGPSPoint.m_y + GPSOffsetPixels.y,
6014 lPredPoint.x + GPSOffsetPixels.x,
6015 lPredPoint.y + GPSOffsetPixels.y);
6016 }
6017
6018 if (g_cog_predictor_endmarker) {
6019 // Prepare COG predictor endpoint icon
6020 double png_pred_icon_scale_factor = .4;
6021 if (g_ShipScaleFactorExp > 1.0)
6022 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6023 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6024
6025 wxPoint icon[4];
6026
6027 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6028 (float)(lPredPoint.x - lShipMidPoint.x));
6029 cog_rad += (float)PI;
6030
6031 for (int i = 0; i < 4; i++) {
6032 int j = i * 2;
6033 double pxa = (double)(s_png_pred_icon[j]);
6034 double pya = (double)(s_png_pred_icon[j + 1]);
6035
6036 pya *= png_pred_icon_scale_factor;
6037 pxa *= png_pred_icon_scale_factor;
6038
6039 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6040 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6041
6042 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6043 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6044 }
6045
6046 // Render COG endpoint icon
6047 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6048 wxPENSTYLE_SOLID);
6049 dc.SetPen(ppPen1);
6050 dc.SetBrush(wxBrush(cPred));
6051
6052 dc.StrokePolygon(4, icon);
6053 }
6054 }
6055 }
6056
6057 // HDT Predictor
6058 if (b_render_hdt) {
6059 float hdt_dash_length = ref_dim * 0.4;
6060
6061 cPred.Set(g_ownship_HDTpredictor_color);
6062 if (cPred == wxNullColour) cPred = PredColor();
6063 float hdt_width =
6064 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6065 : g_cog_predictor_width * 0.8);
6066 wxDash dash_short[2];
6067 dash_short[0] =
6068 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6069 hdt_width); // Short dash , in mm <---------+
6070 dash_short[1] =
6071 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6072 hdt_width); // Short gap |
6073
6074 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6075 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6076 ppPen2.SetDashes(2, dash_short);
6077
6078 dc.SetPen(ppPen2);
6079 dc.StrokeLine(
6080 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6081 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6082
6083 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6084 dc.SetPen(ppPen1);
6085 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6086
6087 if (g_ownship_HDTpredictor_endmarker) {
6088 double nominal_circle_size_pixels = wxMax(
6089 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6090
6091 // Scale the circle to ChartScaleFactor, slightly softened....
6092 if (g_ShipScaleFactorExp > 1.0)
6093 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6094
6095 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6096 lHeadPoint.y + GPSOffsetPixels.y,
6097 nominal_circle_size_pixels / 2);
6098 }
6099 }
6100
6101 // Draw radar rings if activated
6102 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6103 double factor = 1.00;
6104 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6105 factor = 1 / 1.852;
6106 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6107 if (std::isnan(gSog))
6108 factor = 0.0;
6109 else
6110 factor = gSog / 60;
6111 }
6112 factor *= g_fNavAidRadarRingsStep;
6113
6114 double tlat, tlon;
6115 wxPoint r;
6116 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6117 GetCanvasPointPix(tlat, tlon, &r);
6118
6119 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6120 pow((double)(lGPSPoint.m_y - r.y), 2));
6121 int pix_radius = (int)lpp;
6122
6123 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6124
6125 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6126
6127 dc.SetPen(ppPen1);
6128 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6129
6130 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6131 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6132 }
6133}
6134
6135void ChartCanvas::ComputeShipScaleFactor(
6136 float icon_hdt, int ownShipWidth, int ownShipLength,
6137 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6138 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6139 float screenResolution = m_pix_per_mm;
6140
6141 // Calculate the true ship length in exact pixels
6142 double ship_bow_lat, ship_bow_lon;
6143 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6144 &ship_bow_lat, &ship_bow_lon);
6145 wxPoint lShipBowPoint;
6146 wxPoint2DDouble b_point =
6147 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6148 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6149
6150 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6151 powf((float)(b_point.m_y - a_point.m_y), 2));
6152
6153 // And in mm
6154 float shipLength_mm = shipLength_px / screenResolution;
6155
6156 // Set minimum ownship drawing size
6157 float ownship_min_mm = g_n_ownship_min_mm;
6158 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6159
6160 // Calculate Nautical Miles distance from midships to gps antenna
6161 float hdt_ant = icon_hdt + 180.;
6162 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6163 float dx = g_n_gps_antenna_offset_x / 1852.;
6164 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6165 {
6166 hdt_ant = icon_hdt;
6167 dy = -dy;
6168 }
6169
6170 // If the drawn ship size is going to be clamped, adjust the gps antenna
6171 // offsets
6172 if (shipLength_mm < ownship_min_mm) {
6173 dy /= shipLength_mm / ownship_min_mm;
6174 dx /= shipLength_mm / ownship_min_mm;
6175 }
6176
6177 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6178
6179 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6180 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6181 &ship_mid_lon1);
6182
6183 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6184 &lShipMidPoint);
6185
6186 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6187 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6188
6189 float scale_factor = shipLength_px / ownShipLength;
6190
6191 // Calculate a scale factor that would produce a reasonably sized icon
6192 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6193
6194 // And choose the correct one
6195 scale_factor = wxMax(scale_factor, scale_factor_min);
6196
6197 scale_factor_y = scale_factor;
6198 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6199 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6200}
6201
6202void ChartCanvas::ShipDraw(ocpnDC &dc) {
6203 if (!GetVP().IsValid()) return;
6204
6205 wxPoint GPSOffsetPixels(0, 0);
6206 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6207
6208 // COG/SOG may be undefined in NMEA data stream
6209 float pCog = std::isnan(gCog) ? 0 : gCog;
6210 float pSog = std::isnan(gSog) ? 0 : gSog;
6211
6212 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6213
6214 lShipMidPoint = lGPSPoint;
6215
6216 // Draw the icon rotated to the COG
6217 // or to the Hdt if available
6218 float icon_hdt = pCog;
6219 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6220
6221 // COG may be undefined in NMEA data stream
6222 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6223
6224 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6225 // predictor
6226 double osd_head_lat, osd_head_lon;
6227 wxPoint osd_head_point;
6228
6229 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6230 &osd_head_lon);
6231
6232 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6233
6234 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6235 (float)(osd_head_point.x - lShipMidPoint.m_x));
6236 icon_rad += (float)PI;
6237
6238 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6239
6240 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6241 // nominal size and is just barely outside the viewport ....
6242 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6243
6244 // TODO: fix to include actual size of boat that will be rendered
6245 int img_height = 0;
6246 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6247 if (GetVP().chart_scale >
6248 300000) // According to S52, this should be 50,000
6249 {
6250 ShipDrawLargeScale(dc, lShipMidPoint);
6251 img_height = 20;
6252 } else {
6253 wxImage pos_image;
6254
6255 // Substitute user ownship image if found
6256 if (m_pos_image_user)
6257 pos_image = m_pos_image_user->Copy();
6258 else if (SHIP_NORMAL == m_ownship_state)
6259 pos_image = m_pos_image_red->Copy();
6260 if (SHIP_LOWACCURACY == m_ownship_state)
6261 pos_image = m_pos_image_yellow->Copy();
6262 else if (SHIP_NORMAL != m_ownship_state)
6263 pos_image = m_pos_image_grey->Copy();
6264
6265 // Substitute user ownship image if found
6266 if (m_pos_image_user) {
6267 pos_image = m_pos_image_user->Copy();
6268
6269 if (SHIP_LOWACCURACY == m_ownship_state)
6270 pos_image = m_pos_image_user_yellow->Copy();
6271 else if (SHIP_NORMAL != m_ownship_state)
6272 pos_image = m_pos_image_user_grey->Copy();
6273 }
6274
6275 img_height = pos_image.GetHeight();
6276
6277 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6278 g_OwnShipIconType > 0) // use large ship
6279 {
6280 int ownShipWidth = 22; // Default values from s_ownship_icon
6281 int ownShipLength = 84;
6282 if (g_OwnShipIconType == 1) {
6283 ownShipWidth = pos_image.GetWidth();
6284 ownShipLength = pos_image.GetHeight();
6285 }
6286
6287 float scale_factor_x, scale_factor_y;
6288 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6289 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6290 scale_factor_x, scale_factor_y);
6291
6292 if (g_OwnShipIconType == 1) { // Scaled bitmap
6293 pos_image.Rescale(ownShipWidth * scale_factor_x,
6294 ownShipLength * scale_factor_y,
6295 wxIMAGE_QUALITY_HIGH);
6296 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6297 wxImage rot_image =
6298 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6299
6300 // Simple sharpening algorithm.....
6301 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6302 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6303 if (rot_image.GetAlpha(ip, jp) > 64)
6304 rot_image.SetAlpha(ip, jp, 255);
6305
6306 wxBitmap os_bm(rot_image);
6307
6308 int w = os_bm.GetWidth();
6309 int h = os_bm.GetHeight();
6310 img_height = h;
6311
6312 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6313 lShipMidPoint.m_y - h / 2, true);
6314
6315 // Maintain dirty box,, missing in __WXMSW__ library
6316 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6317 lShipMidPoint.m_y - h / 2);
6318 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6319 lShipMidPoint.m_y - h / 2 + h);
6320 }
6321
6322 else if (g_OwnShipIconType == 2) { // Scaled Vector
6323 wxPoint ownship_icon[10];
6324
6325 for (int i = 0; i < 10; i++) {
6326 int j = i * 2;
6327 float pxa = (float)(s_ownship_icon[j]);
6328 float pya = (float)(s_ownship_icon[j + 1]);
6329 pya *= scale_factor_y;
6330 pxa *= scale_factor_x;
6331
6332 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6333 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6334
6335 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6336 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6337 }
6338
6339 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6340 dc.SetPen(ppPen1);
6341 dc.SetBrush(wxBrush(ShipColor()));
6342
6343 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6344
6345 // draw reference point (midships) cross
6346 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6347 ownship_icon[7].y);
6348 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6349 ownship_icon[9].y);
6350 }
6351
6352 img_height = ownShipLength * scale_factor_y;
6353
6354 // Reference point, where the GPS antenna is
6355 int circle_rad = 3;
6356 if (m_pos_image_user) circle_rad = 1;
6357
6358 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6359 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6360 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6361 } else { // Fixed bitmap icon.
6362 /* non opengl, or suboptimal opengl via ocpndc: */
6363 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6364 wxImage rot_image =
6365 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6366
6367 // Simple sharpening algorithm.....
6368 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6369 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6370 if (rot_image.GetAlpha(ip, jp) > 64)
6371 rot_image.SetAlpha(ip, jp, 255);
6372
6373 wxBitmap os_bm(rot_image);
6374
6375 if (g_ShipScaleFactorExp > 1) {
6376 wxImage scaled_image = os_bm.ConvertToImage();
6377 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6378 1.0; // soften the scale factor a bit
6379 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6380 scaled_image.GetHeight() * factor,
6381 wxIMAGE_QUALITY_HIGH));
6382 }
6383 int w = os_bm.GetWidth();
6384 int h = os_bm.GetHeight();
6385 img_height = h;
6386
6387 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6388 lShipMidPoint.m_y - h / 2, true);
6389
6390 // Reference point, where the GPS antenna is
6391 int circle_rad = 3;
6392 if (m_pos_image_user) circle_rad = 1;
6393
6394 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6395 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6396 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6397
6398 // Maintain dirty box,, missing in __WXMSW__ library
6399 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6400 lShipMidPoint.m_y - h / 2);
6401 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6402 lShipMidPoint.m_y - h / 2 + h);
6403 }
6404 } // ownship draw
6405 }
6406
6407 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6408}
6409
6410/* @ChartCanvas::CalcGridSpacing
6411 **
6412 ** Calculate the major and minor spacing between the lat/lon grid
6413 **
6414 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6415 *window
6416 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6417 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6418 ** @return [void]
6419 */
6420void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6421 float &MinorSpacing) {
6422 // table for calculating the distance between the grids
6423 // [0] view_scale ppm
6424 // [1] spacing between major grid lines in degrees
6425 // [2] spacing between minor grid lines in degrees
6426 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6427 {.000001f, 45.0f, 15.0f},
6428 {.0002f, 30.0f, 10.0f},
6429 {.0003f, 10.0f, 2.0f},
6430 {.0008f, 5.0f, 1.0f},
6431 {.001f, 2.0f, 30.0f / 60.0f},
6432 {.003f, 1.0f, 20.0f / 60.0f},
6433 {.006f, 0.5f, 10.0f / 60.0f},
6434 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6435 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6436 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6437 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6438 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6439 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6440 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6441 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6442
6443 unsigned int tabi;
6444 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6445 if (view_scale_ppm < lltab[tabi][0]) break;
6446 MajorSpacing = lltab[tabi][1]; // major latitude distance
6447 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6448 return;
6449}
6450/* @ChartCanvas::CalcGridText *************************************
6451 **
6452 ** Calculates text to display at the major grid lines
6453 **
6454 ** @param [r] latlon [float] latitude or longitude of grid line
6455 ** @param [r] spacing [float] distance between two major grid lines
6456 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6457 **
6458 ** @return
6459 */
6460
6461wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6462 int deg = (int)fabs(latlon); // degrees
6463 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6464 char postfix;
6465
6466 // calculate postfix letter (NSEW)
6467 if (latlon > 0.0) {
6468 if (bPostfix) {
6469 postfix = 'N';
6470 } else {
6471 postfix = 'E';
6472 }
6473 } else if (latlon < 0.0) {
6474 if (bPostfix) {
6475 postfix = 'S';
6476 } else {
6477 postfix = 'W';
6478 }
6479 } else {
6480 postfix = ' '; // no postfix for equator and greenwich
6481 }
6482 // calculate text, display minutes only if spacing is smaller than one degree
6483
6484 wxString ret;
6485 if (spacing >= 1.0) {
6486 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6487 } else if (spacing >= (1.0 / 60.0)) {
6488 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6489 } else {
6490 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6491 }
6492
6493 return ret;
6494}
6495
6496/* @ChartCanvas::GridDraw *****************************************
6497 **
6498 ** Draws major and minor Lat/Lon Grid on the chart
6499 ** - distance between Grid-lm ines are calculated automatic
6500 ** - major grid lines will be across the whole chart window
6501 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6502 **
6503 ** @param [w] dc [wxDC&] the wx drawing context
6504 **
6505 ** @return [void]
6506 ************************************************************************/
6507void ChartCanvas::GridDraw(ocpnDC &dc) {
6508 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6509
6510 double nlat, elon, slat, wlon;
6511 float lat, lon;
6512 float dlon;
6513 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6514 wxCoord w, h;
6515 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6516 dc.SetPen(GridPen);
6517 if (!m_pgridFont) SetupGridFont();
6518 dc.SetFont(*m_pgridFont);
6519 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6520
6521 w = m_canvas_width;
6522 h = m_canvas_height;
6523
6524 GetCanvasPixPoint(0, 0, nlat,
6525 wlon); // get lat/lon of upper left point of the window
6526 GetCanvasPixPoint(w, h, slat,
6527 elon); // get lat/lon of lower right point of the window
6528 dlon =
6529 elon -
6530 wlon; // calculate how many degrees of longitude are shown in the window
6531 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6532 {
6533 dlon = dlon + 360.0;
6534 }
6535 // calculate distance between latitude grid lines
6536 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6537
6538 // calculate position of first major latitude grid line
6539 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6540
6541 // Draw Major latitude grid lines and text
6542 while (lat < nlat) {
6543 wxPoint r;
6544 wxString st =
6545 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6546 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6547 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6548 dc.DrawText(st, 0, r.y); // draw text
6549 lat = lat + gridlatMajor;
6550
6551 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6552 }
6553
6554 // calculate position of first minor latitude grid line
6555 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6556
6557 // Draw minor latitude grid lines
6558 while (lat < nlat) {
6559 wxPoint r;
6560 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6561 dc.DrawLine(0, r.y, 10, r.y, false);
6562 dc.DrawLine(w - 10, r.y, w, r.y, false);
6563 lat = lat + gridlatMinor;
6564 }
6565
6566 // calculate distance between grid lines
6567 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6568
6569 // calculate position of first major latitude grid line
6570 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6571
6572 // draw major longitude grid lines
6573 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6574 wxPoint r;
6575 wxString st = CalcGridText(lon, gridlonMajor, false);
6576 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6577 dc.DrawLine(r.x, 0, r.x, h, false);
6578 dc.DrawText(st, r.x, 0);
6579 lon = lon + gridlonMajor;
6580 if (lon > 180.0) {
6581 lon = lon - 360.0;
6582 }
6583
6584 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6585 }
6586
6587 // calculate position of first minor longitude grid line
6588 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6589 // draw minor longitude grid lines
6590 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6591 wxPoint r;
6592 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6593 dc.DrawLine(r.x, 0, r.x, 10, false);
6594 dc.DrawLine(r.x, h - 10, r.x, h, false);
6595 lon = lon + gridlonMinor;
6596 if (lon > 180.0) {
6597 lon = lon - 360.0;
6598 }
6599 }
6600}
6601
6602void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6603 if (0 ) {
6604 double blat, blon, tlat, tlon;
6605 wxPoint r;
6606
6607 int x_origin = m_bDisplayGrid ? 60 : 20;
6608 int y_origin = m_canvas_height - 50;
6609
6610 float dist;
6611 int count;
6612 wxPen pen1, pen2;
6613
6614 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6615 {
6616 dist = 10.0;
6617 count = 5;
6618 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6619 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6620 } else // Draw 1 mile scale as SCALEB10
6621 {
6622 dist = 1.0;
6623 count = 10;
6624 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6625 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6626 }
6627
6628 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6629 double rotation = -VPoint.rotation;
6630
6631 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6632 GetCanvasPointPix(tlat, tlon, &r);
6633 int l1 = (y_origin - r.y) / count;
6634
6635 for (int i = 0; i < count; i++) {
6636 int y = l1 * i;
6637 if (i & 1)
6638 dc.SetPen(pen1);
6639 else
6640 dc.SetPen(pen2);
6641
6642 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6643 }
6644 } else {
6645 double blat, blon, tlat, tlon;
6646
6647 int x_origin = 5.0 * GetPixPerMM();
6648 int chartbar_height = GetChartbarHeight();
6649 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6650 // if (style->chartStatusWindowTransparent)
6651 // chartbar_height = 0;
6652 int y_origin = m_canvas_height - chartbar_height - 5;
6653#ifdef __WXOSX__
6654 if (!g_bopengl)
6655 y_origin =
6656 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6657#endif
6658
6659 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6660 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6661
6662 double d;
6663 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6664 d /= 2;
6665
6666 int unit = g_iDistanceFormat;
6667 if (d < .5 &&
6668 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6669 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6670
6671 // nice number
6672 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6673 float places = floor(logdist), rem = logdist - places;
6674 dist = pow(10, places);
6675
6676 if (rem < .2)
6677 dist /= 5;
6678 else if (rem < .5)
6679 dist /= 2;
6680
6681 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6682 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6683 double rotation = -VPoint.rotation;
6684
6685 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6686 &tlat, &tlon);
6687 wxPoint r;
6688 GetCanvasPointPix(tlat, tlon, &r);
6689 int l1 = r.x - x_origin;
6690
6691 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6692 12); // Store this for later reference
6693
6694 dc.SetPen(pen1);
6695
6696 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6697 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6698 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6699
6700 if (!m_pgridFont) SetupGridFont();
6701 dc.SetFont(*m_pgridFont);
6702 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6703 int w, h;
6704 dc.GetTextExtent(s, &w, &h);
6705 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6706 if (g_bopengl) {
6707 w /= dpi_factor;
6708 h /= dpi_factor;
6709 }
6710 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6711 }
6712}
6713
6714void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6715 // Constants?
6716 double da_min = 2.;
6717 double da_max = 6.;
6718 double ra_min = 0.;
6719 double ra_max = 40.;
6720
6721 wxPen pen_save = dc.GetPen();
6722
6723 wxDateTime now = wxDateTime::Now();
6724
6725 dc.SetPen(pen);
6726
6727 int x0, y0, x1, y1;
6728
6729 x0 = x1 = x + radius; // Start point
6730 y0 = y1 = y;
6731 double angle = 0.;
6732 int i = 0;
6733
6734 while (angle < 360.) {
6735 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6736 angle += da;
6737
6738 if (angle > 360.) angle = 360.;
6739
6740 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6741
6742 double r;
6743 if (i & 1)
6744 r = radius + ra;
6745 else
6746 r = radius - ra;
6747
6748 x1 = (int)(x + cos(angle * PI / 180.) * r);
6749 y1 = (int)(y + sin(angle * PI / 180.) * r);
6750
6751 dc.DrawLine(x0, y0, x1, y1);
6752
6753 x0 = x1;
6754 y0 = y1;
6755
6756 i++;
6757 }
6758
6759 dc.DrawLine(x + radius, y, x1, y1); // closure
6760
6761 dc.SetPen(pen_save);
6762}
6763
6764static bool bAnchorSoundPlaying = false;
6765
6766static void onAnchorSoundFinished(void *ptr) {
6767 o_sound::g_anchorwatch_sound->UnLoad();
6768 bAnchorSoundPlaying = false;
6769}
6770
6771void ChartCanvas::AlertDraw(ocpnDC &dc) {
6772 using namespace o_sound;
6773 // Visual and audio alert for anchorwatch goes here
6774 bool play_sound = false;
6775 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6776 if (AnchorAlertOn1) {
6777 wxPoint TargetPoint;
6779 &TargetPoint);
6780 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6781 TargetPoint.y, 100);
6782 play_sound = true;
6783 }
6784 } else
6785 AnchorAlertOn1 = false;
6786
6787 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6788 if (AnchorAlertOn2) {
6789 wxPoint TargetPoint;
6791 &TargetPoint);
6792 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6793 TargetPoint.y, 100);
6794 play_sound = true;
6795 }
6796 } else
6797 AnchorAlertOn2 = false;
6798
6799 if (play_sound) {
6800 if (!bAnchorSoundPlaying) {
6801 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6802 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6803 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6804 if (g_anchorwatch_sound->IsOk()) {
6805 bAnchorSoundPlaying = true;
6806 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6807 g_anchorwatch_sound->Play();
6808 }
6809 }
6810 }
6811}
6812
6813void ChartCanvas::UpdateShips() {
6814 // Get the rectangle in the current dc which bounds the "ownship" symbol
6815
6816 wxClientDC dc(this);
6817 if (!dc.IsOk()) return;
6818
6819 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6820 if (!test_bitmap.IsOk()) return;
6821
6822 wxMemoryDC temp_dc(test_bitmap);
6823
6824 temp_dc.ResetBoundingBox();
6825 temp_dc.DestroyClippingRegion();
6826 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6827
6828 // Draw the ownship on the temp_dc
6829 ocpnDC ocpndc = ocpnDC(temp_dc);
6830 ShipDraw(ocpndc);
6831
6832 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6833 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6834 if (p) {
6835 wxPoint px;
6836 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6837 ocpndc.CalcBoundingBox(px.x, px.y);
6838 }
6839 }
6840
6841 ship_draw_rect =
6842 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6843 temp_dc.MaxY() - temp_dc.MinY());
6844
6845 wxRect own_ship_update_rect = ship_draw_rect;
6846
6847 if (!own_ship_update_rect.IsEmpty()) {
6848 // The required invalidate rectangle is the union of the last drawn
6849 // rectangle and this drawn rectangle
6850 own_ship_update_rect.Union(ship_draw_last_rect);
6851 own_ship_update_rect.Inflate(2);
6852 }
6853
6854 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6855
6856 ship_draw_last_rect = ship_draw_rect;
6857
6858 temp_dc.SelectObject(wxNullBitmap);
6859}
6860
6861void ChartCanvas::UpdateAlerts() {
6862 // Get the rectangle in the current dc which bounds the detected Alert
6863 // targets
6864
6865 // Use this dc
6866 wxClientDC dc(this);
6867
6868 // Get dc boundary
6869 int sx, sy;
6870 dc.GetSize(&sx, &sy);
6871
6872 // Need a bitmap
6873 wxBitmap test_bitmap(sx, sy, -1);
6874
6875 // Create a memory DC
6876 wxMemoryDC temp_dc;
6877 temp_dc.SelectObject(test_bitmap);
6878
6879 temp_dc.ResetBoundingBox();
6880 temp_dc.DestroyClippingRegion();
6881 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6882
6883 // Draw the Alert Targets on the temp_dc
6884 ocpnDC ocpndc = ocpnDC(temp_dc);
6885 AlertDraw(ocpndc);
6886
6887 // Retrieve the drawing extents
6888 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6889 temp_dc.MaxX() - temp_dc.MinX(),
6890 temp_dc.MaxY() - temp_dc.MinY());
6891
6892 if (!alert_rect.IsEmpty())
6893 alert_rect.Inflate(2); // clear all drawing artifacts
6894
6895 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6896 // The required invalidate rectangle is the union of the last drawn
6897 // rectangle and this drawn rectangle
6898 wxRect alert_update_rect = alert_draw_rect;
6899 alert_update_rect.Union(alert_rect);
6900
6901 // Invalidate the rectangular region
6902 RefreshRect(alert_update_rect, false);
6903 }
6904
6905 // Save this rectangle for next time
6906 alert_draw_rect = alert_rect;
6907
6908 temp_dc.SelectObject(wxNullBitmap); // clean up
6909}
6910
6911void ChartCanvas::UpdateAIS() {
6912 if (!g_pAIS) return;
6913
6914 // Get the rectangle in the current dc which bounds the detected AIS targets
6915
6916 // Use this dc
6917 wxClientDC dc(this);
6918
6919 // Get dc boundary
6920 int sx, sy;
6921 dc.GetSize(&sx, &sy);
6922
6923 wxRect ais_rect;
6924
6925 // How many targets are there?
6926
6927 // If more than "some number", it will be cheaper to refresh the entire
6928 // screen than to build update rectangles for each target.
6929 if (g_pAIS->GetTargetList().size() > 10) {
6930 ais_rect = wxRect(0, 0, sx, sy); // full screen
6931 } else {
6932 // Need a bitmap
6933 wxBitmap test_bitmap(sx, sy, -1);
6934
6935 // Create a memory DC
6936 wxMemoryDC temp_dc;
6937 temp_dc.SelectObject(test_bitmap);
6938
6939 temp_dc.ResetBoundingBox();
6940 temp_dc.DestroyClippingRegion();
6941 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6942
6943 // Draw the AIS Targets on the temp_dc
6944 ocpnDC ocpndc = ocpnDC(temp_dc);
6945 AISDraw(ocpndc, GetVP(), this);
6946 AISDrawAreaNotices(ocpndc, GetVP(), this);
6947
6948 // Retrieve the drawing extents
6949 ais_rect =
6950 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6951 temp_dc.MaxY() - temp_dc.MinY());
6952
6953 if (!ais_rect.IsEmpty())
6954 ais_rect.Inflate(2); // clear all drawing artifacts
6955
6956 temp_dc.SelectObject(wxNullBitmap); // clean up
6957 }
6958
6959 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6960 // The required invalidate rectangle is the union of the last drawn
6961 // rectangle and this drawn rectangle
6962 wxRect ais_update_rect = ais_draw_rect;
6963 ais_update_rect.Union(ais_rect);
6964
6965 // Invalidate the rectangular region
6966 RefreshRect(ais_update_rect, false);
6967 }
6968
6969 // Save this rectangle for next time
6970 ais_draw_rect = ais_rect;
6971}
6972
6973void ChartCanvas::ToggleCPAWarn() {
6974 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6975 wxString mess;
6976 if (g_bCPAWarn) {
6977 g_bTCPA_Max = true;
6978 mess = _("ON");
6979 } else {
6980 g_bTCPA_Max = false;
6981 mess = _("OFF");
6982 }
6983 // Print to status bar if available.
6984 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
6985 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
6986 } else {
6987 if (!g_AisFirstTimeUse) {
6988 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
6989 _("CPA") + " " + mess, 4, 4);
6990 }
6991 }
6992}
6993
6994void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
6995
6996void ChartCanvas::OnSize(wxSizeEvent &event) {
6997 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
6998 // GetClientSize returns the size of the canvas area in logical pixels.
6999 GetClientSize(&m_canvas_width, &m_canvas_height);
7000
7001#ifdef __WXOSX__
7002 // Support scaled HDPI displays.
7003 m_displayScale = GetContentScaleFactor();
7004#endif
7005
7006 // Convert to physical pixels.
7007 m_canvas_width *= m_displayScale;
7008 m_canvas_height *= m_displayScale;
7009
7010 // Resize the current viewport
7011 VPoint.pix_width = m_canvas_width;
7012 VPoint.pix_height = m_canvas_height;
7013 VPoint.SetPixelScale(m_displayScale);
7014
7015 // Get some canvas metrics
7016
7017 // Rescale to current value, in order to rebuild VPoint data
7018 // structures for new canvas size
7020
7021 m_absolute_min_scale_ppm =
7022 m_canvas_width /
7023 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7024
7025 // Inform the parent Frame that I am being resized...
7026 gFrame->ProcessCanvasResize();
7027
7028 // if MUIBar is active, size the bar
7029 // if(g_useMUI && !m_muiBar){ // rebuild if
7030 // necessary
7031 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7032 // m_muiBarHOSize = m_muiBar->GetSize();
7033 // }
7034
7035 if (m_muiBar) {
7036 SetMUIBarPosition();
7037 UpdateFollowButtonState();
7038 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7039 }
7040
7041 // Set up the scroll margins
7042 xr_margin = m_canvas_width * 95 / 100;
7043 xl_margin = m_canvas_width * 5 / 100;
7044 yt_margin = m_canvas_height * 5 / 100;
7045 yb_margin = m_canvas_height * 95 / 100;
7046
7047 if (m_pQuilt)
7048 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7049
7050 // Resize the scratch BM
7051 delete pscratch_bm;
7052 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7053 m_brepaint_piano = true;
7054
7055 // Resize the Route Calculation BM
7056 m_dc_route.SelectObject(wxNullBitmap);
7057 delete proute_bm;
7058 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7059 m_dc_route.SelectObject(*proute_bm);
7060
7061 // Resize the saved Bitmap
7062 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7063
7064 // Resize the working Bitmap
7065 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7066
7067 // Rescale again, to capture all the changes for new canvas size
7069
7070#ifdef ocpnUSE_GL
7071 if (/*g_bopengl &&*/ m_glcc) {
7072 // FIXME (dave) This can go away?
7073 m_glcc->OnSize(event);
7074 }
7075#endif
7076
7077 FormatPianoKeys();
7078 // Invalidate the whole window
7079 ReloadVP();
7080}
7081
7082void ChartCanvas::ProcessNewGUIScale() {
7083 // m_muiBar->Hide();
7084 delete m_muiBar;
7085 m_muiBar = 0;
7086
7087 CreateMUIBar();
7088}
7089
7090void ChartCanvas::CreateMUIBar() {
7091 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7092 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7093 m_muiBar->SetColorScheme(m_cs);
7094 m_muiBarHOSize = m_muiBar->m_size;
7095 }
7096
7097 if (m_muiBar) {
7098 // We need to update the m_bENCGroup flag, not least for the initial
7099 // creation of a MUIBar
7100 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7101
7102 SetMUIBarPosition();
7103 UpdateFollowButtonState();
7104 m_muiBar->UpdateDynamicValues();
7105 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7106 }
7107}
7108
7109void ChartCanvas::SetMUIBarPosition() {
7110 // if MUIBar is active, size the bar
7111 if (m_muiBar) {
7112 // We estimate the piano width based on the canvas width
7113 int pianoWidth = GetClientSize().x * 0.6f;
7114 // If the piano already exists, we can use its exact width
7115 // if(m_Piano)
7116 // pianoWidth = m_Piano->GetWidth();
7117
7118 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7119 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7120 delete m_muiBar;
7121 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7122 m_muiBar->SetColorScheme(m_cs);
7123 }
7124 }
7125
7126 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7127 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7128 delete m_muiBar;
7129 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7130 m_muiBar->SetColorScheme(m_cs);
7131 }
7132 }
7133
7134 m_muiBar->SetBestPosition();
7135 }
7136}
7137
7138void ChartCanvas::DestroyMuiBar() {
7139 if (m_muiBar) {
7140 delete m_muiBar;
7141 m_muiBar = NULL;
7142 }
7143}
7144
7145void ChartCanvas::ShowCompositeInfoWindow(
7146 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7147 if (n_charts > 0) {
7148 if (NULL == m_pCIWin) {
7149 m_pCIWin = new ChInfoWin(this);
7150 m_pCIWin->Hide();
7151 }
7152
7153 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7154 wxString s;
7155
7156 s = _("Composite of ");
7157
7158 wxString s1;
7159 s1.Printf("%d ", n_charts);
7160 if (n_charts > 1)
7161 s1 += _("charts");
7162 else
7163 s1 += _("chart");
7164 s += s1;
7165 s += '\n';
7166
7167 s1.Printf(_("Chart scale"));
7168 s1 += ": ";
7169 wxString s2;
7170 s2.Printf("1:%d\n", scale);
7171 s += s1;
7172 s += s2;
7173
7174 s1 = _("Zoom in for more information");
7175 s += s1;
7176 s += '\n';
7177
7178 int char_width = s1.Length();
7179 int char_height = 3;
7180
7181 if (g_bChartBarEx) {
7182 s += '\n';
7183 int j = 0;
7184 for (int i : index_vector) {
7185 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7186 wxString path = cte.GetFullSystemPath();
7187 s += path;
7188 s += '\n';
7189 char_height++;
7190 char_width = wxMax(char_width, path.Length());
7191 if (j++ >= 9) break;
7192 }
7193 if (j >= 9) {
7194 s += " .\n .\n .\n";
7195 char_height += 3;
7196 }
7197 s += '\n';
7198 char_height += 1;
7199
7200 char_width += 4; // Fluff
7201 }
7202
7203 m_pCIWin->SetString(s);
7204
7205 m_pCIWin->FitToChars(char_width, char_height);
7206
7207 wxPoint p;
7208 p.x = x / GetContentScaleFactor();
7209 if ((p.x + m_pCIWin->GetWinSize().x) >
7210 (m_canvas_width / GetContentScaleFactor()))
7211 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7212 m_pCIWin->GetWinSize().x) /
7213 2; // centered
7214
7215 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7216 4 - m_pCIWin->GetWinSize().y;
7217
7218 m_pCIWin->dbIndex = 0;
7219 m_pCIWin->chart_scale = 0;
7220 m_pCIWin->SetPosition(p);
7221 m_pCIWin->SetBitmap();
7222 m_pCIWin->Refresh();
7223 m_pCIWin->Show();
7224 }
7225 } else {
7226 HideChartInfoWindow();
7227 }
7228}
7229
7230void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7231 if (dbIndex >= 0) {
7232 if (NULL == m_pCIWin) {
7233 m_pCIWin = new ChInfoWin(this);
7234 m_pCIWin->Hide();
7235 }
7236
7237 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7238 wxString s;
7239 ChartBase *pc = NULL;
7240
7241 // TOCTOU race but worst case will reload chart.
7242 // need to lock it or the background spooler may evict charts in
7243 // OpenChartFromDBAndLock
7244 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7245 pc = ChartData->OpenChartFromDBAndLock(
7246 dbIndex, FULL_INIT); // this must come from cache
7247
7248 int char_width, char_height;
7249 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7250 if (pc) ChartData->UnLockCacheChart(dbIndex);
7251
7252 m_pCIWin->SetString(s);
7253 m_pCIWin->FitToChars(char_width, char_height);
7254
7255 wxPoint p;
7256 p.x = x / GetContentScaleFactor();
7257 if ((p.x + m_pCIWin->GetWinSize().x) >
7258 (m_canvas_width / GetContentScaleFactor()))
7259 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7260 m_pCIWin->GetWinSize().x) /
7261 2; // centered
7262
7263 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7264 4 - m_pCIWin->GetWinSize().y;
7265
7266 m_pCIWin->dbIndex = dbIndex;
7267 m_pCIWin->SetPosition(p);
7268 m_pCIWin->SetBitmap();
7269 m_pCIWin->Refresh();
7270 m_pCIWin->Show();
7271 }
7272 } else {
7273 HideChartInfoWindow();
7274 }
7275}
7276
7277void ChartCanvas::HideChartInfoWindow() {
7278 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7279 m_pCIWin->Hide();
7280 m_pCIWin->Destroy();
7281 m_pCIWin = NULL;
7282
7283#ifdef __ANDROID__
7284 androidForceFullRepaint();
7285#endif
7286 }
7287}
7288
7289void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7290 wxMouseEvent ev(wxEVT_MOTION);
7291 ev.m_x = mouse_x;
7292 ev.m_y = mouse_y;
7293 ev.m_leftDown = mouse_leftisdown;
7294
7295 wxEvtHandler *evthp = GetEventHandler();
7296
7297 ::wxPostEvent(evthp, ev);
7298}
7299
7300void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7301 if ((m_panx_target_final - m_panx_target_now) ||
7302 (m_pany_target_final - m_pany_target_now)) {
7303 DoTimedMovementTarget();
7304 } else
7305 DoTimedMovement();
7306}
7307
7308void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7309
7310bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7311 int delta) {
7312 if (m_disable_edge_pan) return false;
7313
7314 bool bft = false;
7315 int pan_margin = m_canvas_width * margin / 100;
7316 int pan_timer_set = 200;
7317 double pan_delta = GetVP().pix_width * delta / 100;
7318 int pan_x = 0;
7319 int pan_y = 0;
7320
7321 if (x > m_canvas_width - pan_margin) {
7322 bft = true;
7323 pan_x = pan_delta;
7324 }
7325
7326 else if (x < pan_margin) {
7327 bft = true;
7328 pan_x = -pan_delta;
7329 }
7330
7331 if (y < pan_margin) {
7332 bft = true;
7333 pan_y = -pan_delta;
7334 }
7335
7336 else if (y > m_canvas_height - pan_margin) {
7337 bft = true;
7338 pan_y = pan_delta;
7339 }
7340
7341 // Of course, if dragging, and the mouse left button is not down, we must
7342 // stop the event injection
7343 if (bdragging) {
7344 if (!g_btouch) {
7345 wxMouseState state = ::wxGetMouseState();
7346#if wxCHECK_VERSION(3, 0, 0)
7347 if (!state.LeftIsDown())
7348#else
7349 if (!state.LeftDown())
7350#endif
7351 bft = false;
7352 }
7353 }
7354 if ((bft) && !pPanTimer->IsRunning()) {
7355 PanCanvas(pan_x, pan_y);
7356 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7357 return true;
7358 }
7359
7360 // This mouse event must not be due to pan timer event injector
7361 // Mouse is out of the pan zone, so prevent any orphan event injection
7362 if ((!bft) && pPanTimer->IsRunning()) {
7363 pPanTimer->Stop();
7364 }
7365
7366 return (false);
7367}
7368
7369// Look for waypoints at the current position.
7370// Used to determine what a mouse event should act on.
7371
7372void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7373 bool setBeingEdited) {
7374 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7375 m_pRoutePointEditTarget = NULL;
7376 m_pFoundPoint = NULL;
7377
7378 SelectItem *pFind = NULL;
7379 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7380 SelectableItemList SelList = pSelect->FindSelectionList(
7381 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7382 for (SelectItem *pFind : SelList) {
7383 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7384
7385 // Get an array of all routes using this point
7386 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7387 // TODO: delete m_pEditRouteArray after use?
7388
7389 // Use route array to determine actual visibility for the point
7390 bool brp_viz = false;
7391 if (m_pEditRouteArray) {
7392 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7393 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7394 if (pr->IsVisible()) {
7395 brp_viz = true;
7396 break;
7397 }
7398 }
7399 } else
7400 brp_viz = frp->IsVisible(); // isolated point
7401
7402 if (brp_viz) {
7403 // Use route array to rubberband all affected routes
7404 if (m_pEditRouteArray) // Editing Waypoint as part of route
7405 {
7406 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7407 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7408 pr->m_bIsBeingEdited = setBeingEdited;
7409 }
7410 m_bRouteEditing = setBeingEdited;
7411 } else // editing Mark
7412 {
7413 frp->m_bRPIsBeingEdited = setBeingEdited;
7414 m_bMarkEditing = setBeingEdited;
7415 }
7416
7417 m_pRoutePointEditTarget = frp;
7418 m_pFoundPoint = pFind;
7419 break; // out of the while(node)
7420 }
7421 } // for (SelectItem...
7422}
7423std::shared_ptr<HostApi121::PiPointContext>
7424ChartCanvas::GetCanvasContextAtPoint(int x, int y) {
7425 // General Right Click
7426 // Look for selectable objects
7427 double slat, slon;
7428 GetCanvasPixPoint(x, y, slat, slon);
7429
7430 SelectItem *pFindAIS;
7431 SelectItem *pFindRP;
7432 SelectItem *pFindRouteSeg;
7433 SelectItem *pFindTrackSeg;
7434 SelectItem *pFindCurrent = NULL;
7435 SelectItem *pFindTide = NULL;
7436
7437 // Get all the selectable things at the selected point
7438 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7439 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7440 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7441 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7442 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7443
7444 if (m_bShowCurrent)
7445 pFindCurrent =
7446 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7447
7448 if (m_bShowTide) // look for tide stations
7449 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7450
7451 int seltype = 0;
7452
7453 // Try for AIS targets first
7454 int FoundAIS_MMSI = 0;
7455 if (pFindAIS) {
7456 FoundAIS_MMSI = pFindAIS->GetUserData();
7457
7458 // Make sure the target data is available
7459 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7460 seltype |= SELTYPE_AISTARGET;
7461 }
7462
7463 // Now the various Route Parts
7464
7465 RoutePoint *FoundRoutePoint = NULL;
7466 Route *SelectedRoute = NULL;
7467
7468 if (pFindRP) {
7469 RoutePoint *pFirstVizPoint = NULL;
7470 RoutePoint *pFoundActiveRoutePoint = NULL;
7471 RoutePoint *pFoundVizRoutePoint = NULL;
7472 Route *pSelectedActiveRoute = NULL;
7473 Route *pSelectedVizRoute = NULL;
7474
7475 // There is at least one routepoint, so get the whole list
7476 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7477 SelectableItemList SelList =
7478 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7479 for (SelectItem *pFindSel : SelList) {
7480 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7481
7482 // Get an array of all routes using this point
7483 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7484
7485 // Use route array (if any) to determine actual visibility for this point
7486 bool brp_viz = false;
7487 if (proute_array) {
7488 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7489 Route *pr = (Route *)proute_array->Item(ir);
7490 if (pr->IsVisible()) {
7491 brp_viz = true;
7492 break;
7493 }
7494 }
7495 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7496 // but still exists as a waypoint
7497 brp_viz = prp->IsVisible(); // so treat as isolated point
7498
7499 } else
7500 brp_viz = prp->IsVisible(); // isolated point
7501
7502 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7503
7504 // Use route array to choose the appropriate route
7505 // Give preference to any active route, otherwise select the first visible
7506 // route in the array for this point
7507 if (proute_array) {
7508 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7509 Route *pr = (Route *)proute_array->Item(ir);
7510 if (pr->m_bRtIsActive) {
7511 pSelectedActiveRoute = pr;
7512 pFoundActiveRoutePoint = prp;
7513 break;
7514 }
7515 }
7516
7517 if (NULL == pSelectedVizRoute) {
7518 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7519 Route *pr = (Route *)proute_array->Item(ir);
7520 if (pr->IsVisible()) {
7521 pSelectedVizRoute = pr;
7522 pFoundVizRoutePoint = prp;
7523 break;
7524 }
7525 }
7526 }
7527
7528 delete proute_array;
7529 }
7530 }
7531
7532 // Now choose the "best" selections
7533 if (pFoundActiveRoutePoint) {
7534 FoundRoutePoint = pFoundActiveRoutePoint;
7535 SelectedRoute = pSelectedActiveRoute;
7536 } else if (pFoundVizRoutePoint) {
7537 FoundRoutePoint = pFoundVizRoutePoint;
7538 SelectedRoute = pSelectedVizRoute;
7539 } else
7540 // default is first visible point in list
7541 FoundRoutePoint = pFirstVizPoint;
7542
7543 if (SelectedRoute) {
7544 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7545 } else if (FoundRoutePoint) {
7546 seltype |= SELTYPE_MARKPOINT;
7547 }
7548
7549 // Highlight the selected point, to verify the proper right click selection
7550#if 0
7551 if (m_pFoundRoutePoint) {
7552 m_pFoundRoutePoint->m_bPtIsSelected = true;
7553 wxRect wp_rect;
7554 RoutePointGui(*m_pFoundRoutePoint)
7555 .CalculateDCRect(m_dc_route, this, &wp_rect);
7556 RefreshRect(wp_rect, true);
7557 }
7558#endif
7559 }
7560
7561 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7562 // routes But call the popup handler with identifier appropriate to the type
7563 if (pFindRouteSeg) // there is at least one select item
7564 {
7565 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7566 SelectableItemList SelList =
7567 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7568
7569 if (NULL == SelectedRoute) // the case where a segment only is selected
7570 {
7571 // Choose the first visible route containing segment in the list
7572 for (SelectItem *pFindSel : SelList) {
7573 Route *pr = (Route *)pFindSel->m_pData3;
7574 if (pr->IsVisible()) {
7575 SelectedRoute = pr;
7576 break;
7577 }
7578 }
7579 }
7580
7581 if (SelectedRoute) {
7582 if (NULL == FoundRoutePoint)
7583 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7584
7585 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7586 seltype |= SELTYPE_ROUTESEGMENT;
7587 }
7588 }
7589
7590 if (pFindTrackSeg) {
7591 m_pSelectedTrack = NULL;
7592 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7593 SelectableItemList SelList =
7594 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7595
7596 // Choose the first visible track containing segment in the list
7597 for (SelectItem *pFindSel : SelList) {
7598 Track *pt = (Track *)pFindSel->m_pData3;
7599 if (pt->IsVisible()) {
7600 m_pSelectedTrack = pt;
7601 break;
7602 }
7603 }
7604 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7605 }
7606
7607 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7608
7609 // Populate the return struct
7610 auto rstruct = std::make_shared<HostApi121::PiPointContext>();
7611 rstruct->object_type = HostApi121::PiContextObjectType::kObjectChart;
7612 rstruct->object_ident = "";
7613
7614 if (seltype == SELTYPE_AISTARGET) {
7615 rstruct->object_type = HostApi121::PiContextObjectType::kObjectAisTarget;
7616 wxString val;
7617 val.Printf("%d", FoundAIS_MMSI);
7618 rstruct->object_ident = val.ToStdString();
7619 } else if (seltype & SELTYPE_MARKPOINT) {
7620 if (FoundRoutePoint) {
7621 rstruct->object_type = HostApi121::PiContextObjectType::kObjectRoutepoint;
7622 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7623 }
7624 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7625 if (SelectedRoute) {
7626 rstruct->object_type =
7627 HostApi121::PiContextObjectType::kObjectRoutesegment;
7628 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7629 }
7630 } else if (seltype & SELTYPE_TRACKSEGMENT) {
7631 if (m_pSelectedTrack) {
7632 rstruct->object_type =
7633 HostApi121::PiContextObjectType::kObjectTracksegment;
7634 rstruct->object_ident = m_pSelectedTrack->m_GUID.ToStdString();
7635 }
7636 }
7637
7638 return rstruct;
7639}
7640
7641void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7642 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7643 singleClickEventIsValid = false;
7644 m_DoubleClickTimer->Stop();
7645}
7646
7647bool leftIsDown;
7648
7649bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7650 if (!m_bChartDragging && !m_bDrawingRoute) {
7651 /*
7652 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7653 * mouse event coordinates are in logical pixels.
7654 */
7655 if (m_Compass && m_Compass->IsShown()) {
7656 wxRect logicalRect = m_Compass->GetLogicalRect();
7657 bool isInCompass = logicalRect.Contains(event.GetPosition());
7658 if (isInCompass || m_mouseWasInCompass) {
7659 if (m_Compass->MouseEvent(event)) {
7660 cursor_region = CENTER;
7661 if (!g_btouch) SetCanvasCursor(event);
7662 m_mouseWasInCompass = isInCompass;
7663 return true;
7664 }
7665 }
7666 m_mouseWasInCompass = isInCompass;
7667 }
7668
7669 if (m_notification_button && m_notification_button->IsShown()) {
7670 wxRect logicalRect = m_notification_button->GetLogicalRect();
7671 bool isinButton = logicalRect.Contains(event.GetPosition());
7672 if (isinButton) {
7673 SetCursor(*pCursorArrow);
7674 if (event.LeftDown()) HandleNotificationMouseClick();
7675 return true;
7676 }
7677 }
7678
7679 if (MouseEventToolbar(event)) return true;
7680
7681 if (MouseEventChartBar(event)) return true;
7682
7683 if (MouseEventMUIBar(event)) return true;
7684
7685 if (MouseEventIENCBar(event)) return true;
7686 }
7687 return false;
7688}
7689
7690void ChartCanvas::HandleNotificationMouseClick() {
7691 if (!m_NotificationsList) {
7692 m_NotificationsList = new NotificationsList(this);
7693
7694 // calculate best size for Notification list
7695 m_NotificationsList->RecalculateSize();
7696 m_NotificationsList->Hide();
7697 }
7698
7699 if (m_NotificationsList->IsShown()) {
7700 m_NotificationsList->Hide();
7701 } else {
7702 m_NotificationsList->RecalculateSize();
7703 m_NotificationsList->ReloadNotificationList();
7704 m_NotificationsList->Show();
7705 }
7706}
7707bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7708 if (!g_bShowChartBar) return false;
7709
7710 if (!m_Piano->MouseEvent(event)) return false;
7711
7712 cursor_region = CENTER;
7713 if (!g_btouch) SetCanvasCursor(event);
7714 return true;
7715}
7716
7717bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7718 if (!IsPrimaryCanvas()) return false;
7719
7720 if (g_MainToolbar) {
7721 if (!g_MainToolbar->MouseEvent(event))
7722 return false;
7723 else
7724 g_MainToolbar->RefreshToolbar();
7725 }
7726
7727 cursor_region = CENTER;
7728 if (!g_btouch) SetCanvasCursor(event);
7729 return true;
7730}
7731
7732bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7733 if (!IsPrimaryCanvas()) return false;
7734
7735 if (g_iENCToolbar) {
7736 if (!g_iENCToolbar->MouseEvent(event))
7737 return false;
7738 else {
7739 g_iENCToolbar->RefreshToolbar();
7740 return true;
7741 }
7742 }
7743 return false;
7744}
7745
7746bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7747 if (m_muiBar) {
7748 if (!m_muiBar->MouseEvent(event)) return false;
7749 }
7750
7751 cursor_region = CENTER;
7752 if (!g_btouch) SetCanvasCursor(event);
7753 if (m_muiBar)
7754 return true;
7755 else
7756 return false;
7757}
7758
7759bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7760 int x, y;
7761
7762 bool bret = false;
7763
7764 event.GetPosition(&x, &y);
7765
7766 x *= m_displayScale;
7767 y *= m_displayScale;
7768
7769 m_MouseDragging = event.Dragging();
7770
7771 // Some systems produce null drag events, where the pointer position has not
7772 // changed from the previous value. Detect this case, and abort further
7773 // processing (FS#1748)
7774#ifdef __WXMSW__
7775 if (event.Dragging()) {
7776 if ((x == mouse_x) && (y == mouse_y)) return true;
7777 }
7778#endif
7779
7780 mouse_x = x;
7781 mouse_y = y;
7782 mouse_leftisdown = event.LeftDown();
7784
7785 // Establish the event region
7786 cursor_region = CENTER;
7787
7788 int chartbar_height = GetChartbarHeight();
7789
7790 if (m_Compass && m_Compass->IsShown() &&
7791 m_Compass->GetRect().Contains(event.GetPosition())) {
7792 cursor_region = CENTER;
7793 } else if (x > xr_margin) {
7794 cursor_region = MID_RIGHT;
7795 } else if (x < xl_margin) {
7796 cursor_region = MID_LEFT;
7797 } else if (y > yb_margin - chartbar_height &&
7798 y < m_canvas_height - chartbar_height) {
7799 cursor_region = MID_TOP;
7800 } else if (y < yt_margin) {
7801 cursor_region = MID_BOT;
7802 } else {
7803 cursor_region = CENTER;
7804 }
7805
7806 if (!g_btouch) SetCanvasCursor(event);
7807
7808 // Protect from leftUp's coming from event handlers in child
7809 // windows who return focus to the canvas.
7810 leftIsDown = event.LeftDown();
7811
7812#ifndef __WXOSX__
7813 if (event.LeftDown()) {
7814 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7815 // The menu bar is temporarily visible due to alt having been pressed.
7816 // Clicking will hide it, and do nothing else.
7817 g_bTempShowMenuBar = false;
7818 parent_frame->ApplyGlobalSettings(false);
7819 return (true);
7820 }
7821 }
7822#endif
7823
7824 // Update modifiers here; some window managers never send the key event
7825 m_modkeys = 0;
7826 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7827 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7828
7829#ifdef __WXMSW__
7830 // TODO Test carefully in other platforms, remove ifdef....
7831 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7832 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7833#endif
7834
7835 event.SetEventObject(this);
7836 if (SendMouseEventToPlugins(event))
7837 return (true); // PlugIn did something, and does not want the canvas to
7838 // do anything else
7839
7840 // Capture LeftUp's and time them, unless it already came from the timer.
7841
7842 // Detect end of chart dragging
7843 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7844 StartChartDragInertia();
7845 }
7846
7847 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7848 !singleClickEventIsValid) {
7849 // Ignore the second LeftUp after the DClick.
7850 if (m_DoubleClickTimer->IsRunning()) {
7851 m_DoubleClickTimer->Stop();
7852 return (true);
7853 }
7854
7855 // Save the event for later running if there is no DClick.
7856 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7857 singleClickEvent = event;
7858 singleClickEventIsValid = true;
7859 return (true);
7860 }
7861
7862 // This logic is necessary on MSW to handle the case where
7863 // a context (right-click) menu is dismissed without action
7864 // by clicking on the chart surface.
7865 // We need to avoid an unintentional pan by eating some clicks...
7866#ifdef __WXMSW__
7867 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7868 if (g_click_stop > 0) {
7869 g_click_stop--;
7870 return (true);
7871 }
7872 }
7873#endif
7874
7875 // Kick off the Rotation control timer
7876 if (GetUpMode() == COURSE_UP_MODE) {
7877 m_b_rot_hidef = false;
7878 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7879 } else
7880 pRotDefTimer->Stop();
7881
7882 // Retrigger the route leg / AIS target popup timer
7883 bool bRoll = !g_btouch;
7884#ifdef __ANDROID__
7885 bRoll = g_bRollover;
7886#endif
7887 if (bRoll) {
7888 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7889 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7890 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7891 m_RolloverPopupTimer.Start(
7892 10,
7893 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7894 else
7895 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7896 }
7897
7898 // Retrigger the cursor tracking timer
7899 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7900
7901// Show cursor position on Status Bar, if present
7902// except for GTK, under which status bar updates are very slow
7903// due to Update() call.
7904// In this case, as a workaround, update the status window
7905// after an interval timer (pCurTrackTimer) pops, which will happen
7906// whenever the mouse has stopped moving for specified interval.
7907// See the method OnCursorTrackTimerEvent()
7908#if !defined(__WXGTK__) && !defined(__WXQT__)
7909 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7910#endif
7911
7912 // Send the current cursor lat/lon to all PlugIns requesting it
7913 if (g_pi_manager) {
7914 // Occasionally, MSW will produce nonsense events on right click....
7915 // This results in an error in cursor geo position, so we skip this case
7916 if ((x >= 0) && (y >= 0))
7917 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7918 }
7919
7920 if (!g_btouch) {
7921 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7922 wxPoint p = ClientToScreen(wxPoint(x, y));
7923 }
7924 }
7925
7926 if (1 ) {
7927 // Route Creation Rubber Banding
7928 if (m_routeState >= 2) {
7929 r_rband.x = x;
7930 r_rband.y = y;
7931 m_bDrawingRoute = true;
7932
7933 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7934 Refresh(false);
7935 }
7936
7937 // Measure Tool Rubber Banding
7938 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7939 r_rband.x = x;
7940 r_rband.y = y;
7941 m_bDrawingRoute = true;
7942
7943 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7944 Refresh(false);
7945 }
7946 }
7947 return bret;
7948}
7949
7950int ChartCanvas::PrepareContextSelections(double lat, double lon) {
7951 // On general Right Click
7952 // Look for selectable objects
7953 double slat = lat;
7954 double slon = lon;
7955
7956#if defined(__WXMAC__) || defined(__ANDROID__)
7957 wxScreenDC sdc;
7958 ocpnDC dc(sdc);
7959#else
7960 wxClientDC cdc(GetParent());
7961 ocpnDC dc(cdc);
7962#endif
7963
7964 SelectItem *pFindAIS;
7965 SelectItem *pFindRP;
7966 SelectItem *pFindRouteSeg;
7967 SelectItem *pFindTrackSeg;
7968 SelectItem *pFindCurrent = NULL;
7969 SelectItem *pFindTide = NULL;
7970
7971 // Deselect any current objects
7972 if (m_pSelectedRoute) {
7973 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7974 m_pSelectedRoute->DeSelectRoute();
7975#ifdef ocpnUSE_GL
7976 if (g_bopengl && m_glcc) {
7977 InvalidateGL();
7978 Update();
7979 } else
7980#endif
7981 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7982 }
7983
7984 if (m_pFoundRoutePoint) {
7985 m_pFoundRoutePoint->m_bPtIsSelected = false;
7986 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
7987 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
7988 }
7989
7992 if (g_btouch && m_pRoutePointEditTarget) {
7993 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7994 m_pRoutePointEditTarget->m_bPtIsSelected = false;
7995 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7996 }
7997
7998 // Get all the selectable things at the cursor
7999 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8000 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8001 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8002 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8003 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8004
8005 if (m_bShowCurrent)
8006 pFindCurrent =
8007 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8008
8009 if (m_bShowTide) // look for tide stations
8010 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8011
8012 int seltype = 0;
8013
8014 // Try for AIS targets first
8015 if (pFindAIS) {
8016 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8017
8018 // Make sure the target data is available
8019 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8020 seltype |= SELTYPE_AISTARGET;
8021 }
8022
8023 // Now examine the various Route parts
8024
8025 m_pFoundRoutePoint = NULL;
8026 if (pFindRP) {
8027 RoutePoint *pFirstVizPoint = NULL;
8028 RoutePoint *pFoundActiveRoutePoint = NULL;
8029 RoutePoint *pFoundVizRoutePoint = NULL;
8030 Route *pSelectedActiveRoute = NULL;
8031 Route *pSelectedVizRoute = NULL;
8032
8033 // There is at least one routepoint, so get the whole list
8034 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8035 SelectableItemList SelList =
8036 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8037 for (SelectItem *pFindSel : SelList) {
8038 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8039
8040 // Get an array of all routes using this point
8041 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8042
8043 // Use route array (if any) to determine actual visibility for this point
8044 bool brp_viz = false;
8045 if (proute_array) {
8046 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8047 Route *pr = (Route *)proute_array->Item(ir);
8048 if (pr->IsVisible()) {
8049 brp_viz = true;
8050 break;
8051 }
8052 }
8053 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8054 // but still exists as a waypoint
8055 brp_viz = prp->IsVisible(); // so treat as isolated point
8056
8057 } else
8058 brp_viz = prp->IsVisible(); // isolated point
8059
8060 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8061
8062 // Use route array to choose the appropriate route
8063 // Give preference to any active route, otherwise select the first visible
8064 // route in the array for this point
8065 m_pSelectedRoute = NULL;
8066 if (proute_array) {
8067 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8068 Route *pr = (Route *)proute_array->Item(ir);
8069 if (pr->m_bRtIsActive) {
8070 pSelectedActiveRoute = pr;
8071 pFoundActiveRoutePoint = prp;
8072 break;
8073 }
8074 }
8075
8076 if (NULL == pSelectedVizRoute) {
8077 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8078 Route *pr = (Route *)proute_array->Item(ir);
8079 if (pr->IsVisible()) {
8080 pSelectedVizRoute = pr;
8081 pFoundVizRoutePoint = prp;
8082 break;
8083 }
8084 }
8085 }
8086
8087 delete proute_array;
8088 }
8089 }
8090
8091 // Now choose the "best" selections
8092 if (pFoundActiveRoutePoint) {
8093 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8094 m_pSelectedRoute = pSelectedActiveRoute;
8095 } else if (pFoundVizRoutePoint) {
8096 m_pFoundRoutePoint = pFoundVizRoutePoint;
8097 m_pSelectedRoute = pSelectedVizRoute;
8098 } else
8099 // default is first visible point in list
8100 m_pFoundRoutePoint = pFirstVizPoint;
8101
8102 if (m_pSelectedRoute) {
8103 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8104 } else if (m_pFoundRoutePoint) {
8105 seltype |= SELTYPE_MARKPOINT;
8106 }
8107
8108 // Highlight the selected point, to verify the proper right click selection
8109 if (m_pFoundRoutePoint) {
8110 m_pFoundRoutePoint->m_bPtIsSelected = true;
8111 wxRect wp_rect;
8112 RoutePointGui(*m_pFoundRoutePoint)
8113 .CalculateDCRect(m_dc_route, this, &wp_rect);
8114 RefreshRect(wp_rect, true);
8115 }
8116 }
8117
8118 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8119 // routes But call the popup handler with identifier appropriate to the type
8120 if (pFindRouteSeg) // there is at least one select item
8121 {
8122 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8123 SelectableItemList SelList =
8124 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8125
8126 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8127 {
8128 // Choose the first visible route containing segment in the list
8129 for (SelectItem *pFindSel : SelList) {
8130 Route *pr = (Route *)pFindSel->m_pData3;
8131 if (pr->IsVisible()) {
8132 m_pSelectedRoute = pr;
8133 break;
8134 }
8135 }
8136 }
8137
8138 if (m_pSelectedRoute) {
8139 if (NULL == m_pFoundRoutePoint)
8140 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8141
8142 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8143 if (m_pSelectedRoute->m_bRtIsSelected) {
8144#ifdef ocpnUSE_GL
8145 if (g_bopengl && m_glcc) {
8146 InvalidateGL();
8147 Update();
8148 } else
8149#endif
8150 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8151 }
8152 seltype |= SELTYPE_ROUTESEGMENT;
8153 }
8154 }
8155
8156 if (pFindTrackSeg) {
8157 m_pSelectedTrack = NULL;
8158 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8159 SelectableItemList SelList =
8160 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8161
8162 // Choose the first visible track containing segment in the list
8163 for (SelectItem *pFindSel : SelList) {
8164 Track *pt = (Track *)pFindSel->m_pData3;
8165 if (pt->IsVisible()) {
8166 m_pSelectedTrack = pt;
8167 break;
8168 }
8169 }
8170 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8171 }
8172
8173#if 0 // disable tide and current graph on right click
8174 {
8175 if (pFindCurrent) {
8176 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8177 seltype |= SELTYPE_CURRENTPOINT;
8178 }
8179
8180 else if (pFindTide) {
8181 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8182 seltype |= SELTYPE_TIDEPOINT;
8183 }
8184 }
8185#endif
8186
8187 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8188
8189 return seltype;
8190}
8191
8192IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8193 // There may be multiple current entries at the same point.
8194 // For example, there often is a current substation (with directions
8195 // specified) co-located with its master. We want to select the
8196 // substation, so that the direction will be properly indicated on the
8197 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8198 // substation)
8199 IDX_entry *pIDX_best_candidate;
8200
8201 SelectItem *pFind = NULL;
8202 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8203 SelectableItemList SelList =
8204 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8205
8206 // Default is first entry
8207 pFind = *SelList.begin();
8208 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8209
8210 auto node = SelList.begin();
8211 if (SelList.size() > 1) {
8212 for (++node; node != SelList.end(); ++node) {
8213 pFind = *node;
8214 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8215 if (pIDX_candidate->IDX_type == 'c') {
8216 pIDX_best_candidate = pIDX_candidate;
8217 break;
8218 }
8219 } // while (node)
8220 } else {
8221 pFind = *SelList.begin();
8222 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8223 }
8224
8225 return pIDX_best_candidate;
8226}
8227void ChartCanvas::CallPopupMenu(int x, int y) {
8228 last_drag.x = x;
8229 last_drag.y = y;
8230 if (m_routeState) { // creating route?
8231 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8232 return;
8233 }
8234
8236
8237 // If tide or current point is selected, then show the TC dialog immediately
8238 // without context menu
8239 if (SELTYPE_CURRENTPOINT == seltype) {
8240 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8241 Refresh(false);
8242 return;
8243 }
8244
8245 if (SELTYPE_TIDEPOINT == seltype) {
8246 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8247 Refresh(false);
8248 return;
8249 }
8250
8251 InvokeCanvasMenu(x, y, seltype);
8252
8253 // Clean up if not deleted in InvokeCanvasMenu
8254 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8255 m_pSelectedRoute->m_bRtIsSelected = false;
8256 }
8257
8258 m_pSelectedRoute = NULL;
8259
8260 if (m_pFoundRoutePoint) {
8261 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8262 m_pFoundRoutePoint->m_bPtIsSelected = false;
8263 }
8264 m_pFoundRoutePoint = NULL;
8265
8266 Refresh(true);
8267 // Refresh(false); // needed for MSW, not GTK Why??
8268}
8269
8270bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8271 // For now just bail out completely if the point clicked is not on the chart
8272 if (std::isnan(m_cursor_lat)) return false;
8273
8274 // Mouse Clicks
8275 bool ret = false; // return true if processed
8276
8277 int x, y, mx, my;
8278 event.GetPosition(&x, &y);
8279 mx = x;
8280 my = y;
8281
8282 // Calculate meaningful SelectRadius
8283 float SelectRadius;
8284 SelectRadius = g_Platform->GetSelectRadiusPix() /
8285 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8286
8288 // We start with Double Click processing. The first left click just starts a
8289 // timer and is remembered, then we actually do something if there is a
8290 // LeftDClick. If there is, the two single clicks are ignored.
8291
8292 if (event.LeftDClick() && (cursor_region == CENTER)) {
8293 m_DoubleClickTimer->Start();
8294 singleClickEventIsValid = false;
8295
8296 double zlat, zlon;
8298 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8299
8300 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8301 if (m_bShowAIS) {
8302 SelectItem *pFindAIS;
8303 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8304
8305 if (pFindAIS) {
8306 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8307 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8308 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8309 }
8310 return true;
8311 }
8312 }
8313
8314 SelectableItemList rpSelList =
8315 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8316 bool b_onRPtarget = false;
8317 for (SelectItem *pFind : rpSelList) {
8318 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8319 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8320 b_onRPtarget = true;
8321 break;
8322 }
8323 }
8324
8325 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8326
8327 // Get and honor the plugin API ContextMenuMask
8328 std::unique_ptr<HostApi> host_api = GetHostApi();
8329 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8330
8331 if (m_pRoutePointEditTarget) {
8332 if (b_onRPtarget) {
8333 if ((api_121->GetContextMenuMask() &
8334 api_121->kContextMenuDisableWaypoint))
8335 return true;
8336 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8337 return true;
8338 } else {
8339 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8340 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8341 if (g_btouch)
8342 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8343 wxRect wp_rect;
8344 RoutePointGui(*m_pRoutePointEditTarget)
8345 .CalculateDCRect(m_dc_route, this, &wp_rect);
8346 m_pRoutePointEditTarget = NULL; // cancel selection
8347 RefreshRect(wp_rect, true);
8348 return true;
8349 }
8350 } else {
8351 auto node = rpSelList.begin();
8352 if (node != rpSelList.end()) {
8353 SelectItem *pFind = *node;
8354 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8355 if (frp) {
8356 wxArrayPtrVoid *proute_array =
8358
8359 // Use route array (if any) to determine actual visibility for this
8360 // point
8361 bool brp_viz = false;
8362 if (proute_array) {
8363 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8364 Route *pr = (Route *)proute_array->Item(ir);
8365 if (pr->IsVisible()) {
8366 brp_viz = true;
8367 break;
8368 }
8369 }
8370 delete proute_array;
8371 if (!brp_viz &&
8372 frp->IsShared()) // is not visible as part of route, but
8373 // still exists as a waypoint
8374 brp_viz = frp->IsVisible(); // so treat as isolated point
8375 } else
8376 brp_viz = frp->IsVisible(); // isolated point
8377
8378 if (brp_viz) {
8379 if ((api_121->GetContextMenuMask() &
8380 api_121->kContextMenuDisableWaypoint))
8381 return true;
8382
8383 ShowMarkPropertiesDialog(frp);
8384 return true;
8385 }
8386 }
8387 }
8388 }
8389
8390 SelectItem *cursorItem;
8391
8392 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8393 if (cursorItem) {
8394 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8395 return true;
8396 Route *pr = (Route *)cursorItem->m_pData3;
8397 if (pr->IsVisible()) {
8398 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8399 return true;
8400 }
8401 }
8402
8403 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8404 if (cursorItem) {
8405 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8406 return true;
8407 Track *pt = (Track *)cursorItem->m_pData3;
8408 if (pt->IsVisible()) {
8409 ShowTrackPropertiesDialog(pt);
8410 return true;
8411 }
8412 }
8413
8414 // Tide and current points
8415 SelectItem *pFindCurrent = NULL;
8416 SelectItem *pFindTide = NULL;
8417
8418 if (m_bShowCurrent) { // look for current stations
8419 pFindCurrent =
8420 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8421 if (pFindCurrent) {
8422 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8423 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8424 Refresh(false);
8425 return true;
8426 }
8427 }
8428
8429 if (m_bShowTide) { // look for tide stations
8430 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8431 if (pFindTide) {
8432 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8433 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8434 Refresh(false);
8435 return true;
8436 }
8437 }
8438
8439 // Found no object to act on, so show chart info.
8440 ShowObjectQueryWindow(x, y, zlat, zlon);
8441 return true;
8442 }
8443
8445 if (event.LeftDown()) {
8446 // This really should not be needed, but....
8447 // on Windows, when using wxAUIManager, sometimes the focus is lost
8448 // when clicking into another pane, e.g.the AIS target list, and then back
8449 // to this pane. Oddly, some mouse events are not lost, however. Like this
8450 // one....
8451 SetFocus();
8452
8453 last_drag.x = mx;
8454 last_drag.y = my;
8455 leftIsDown = true;
8456
8457 if (!g_btouch) {
8458 if (m_routeState) // creating route?
8459 {
8460 double rlat, rlon;
8461 bool appending = false;
8462 bool inserting = false;
8463 Route *tail = 0;
8464
8465 SetCursor(*pCursorPencil);
8466 rlat = m_cursor_lat;
8467 rlon = m_cursor_lon;
8468
8469 m_bRouteEditing = true;
8470
8471 if (m_routeState == 1) {
8472 m_pMouseRoute = new Route();
8473 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8474 pRouteList->push_back(m_pMouseRoute);
8475 r_rband.x = x;
8476 r_rband.y = y;
8477 }
8478
8479 // Check to see if there is a nearby point which may be reused
8480 RoutePoint *pMousePoint = NULL;
8481
8482 // Calculate meaningful SelectRadius
8483 double nearby_radius_meters =
8484 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8485
8486 RoutePoint *pNearbyPoint =
8487 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8488 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8489 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8490 wxArrayPtrVoid *proute_array =
8491 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8492
8493 // Use route array (if any) to determine actual visibility for this
8494 // point
8495 bool brp_viz = false;
8496 if (proute_array) {
8497 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8498 Route *pr = (Route *)proute_array->Item(ir);
8499 if (pr->IsVisible()) {
8500 brp_viz = true;
8501 break;
8502 }
8503 }
8504 delete proute_array;
8505 if (!brp_viz &&
8506 pNearbyPoint->IsShared()) // is not visible as part of route,
8507 // but still exists as a waypoint
8508 brp_viz =
8509 pNearbyPoint->IsVisible(); // so treat as isolated point
8510 } else
8511 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8512
8513 if (brp_viz) {
8514 wxString msg = _("Use nearby waypoint?");
8515 // Don't add a mark without name to the route. Name it if needed
8516 const bool noname(pNearbyPoint->GetName() == "");
8517 if (noname) {
8518 msg =
8519 _("Use nearby nameless waypoint and name it M with"
8520 " a unique number?");
8521 }
8522 // Avoid route finish on focus change for message dialog
8523 m_FinishRouteOnKillFocus = false;
8524 int dlg_return =
8525 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8526 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8527 m_FinishRouteOnKillFocus = true;
8528 if (dlg_return == wxID_YES) {
8529 if (noname) {
8530 if (m_pMouseRoute) {
8531 int last_wp_num = m_pMouseRoute->GetnPoints();
8532 // AP-ECRMB will truncate to 6 characters
8533 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8534 wxString wp_name = wxString::Format(
8535 "M%002i-%s", last_wp_num + 1, guid_short);
8536 pNearbyPoint->SetName(wp_name);
8537 } else
8538 pNearbyPoint->SetName("WPXX");
8539 }
8540 pMousePoint = pNearbyPoint;
8541
8542 // Using existing waypoint, so nothing to delete for undo.
8543 if (m_routeState > 1)
8544 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8545 Undo_HasParent, NULL);
8546
8547 tail =
8548 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8549 bool procede = false;
8550 if (tail) {
8551 procede = true;
8552 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8553 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8554 procede = false;
8555 }
8556
8557 if (procede) {
8558 int dlg_return;
8559 m_FinishRouteOnKillFocus = false;
8560 if (m_routeState ==
8561 1) { // first point in new route, preceeding route to be
8562 // added? Not touch case
8563
8564 wxString dmsg =
8565 _("Insert first part of this route in the new route?");
8566 if (tail->GetIndexOf(pMousePoint) ==
8567 tail->GetnPoints()) // Starting on last point of another
8568 // route?
8569 dmsg = _("Insert this route in the new route?");
8570
8571 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8572 dlg_return = OCPNMessageBox(
8573 this, dmsg, _("OpenCPN Route Create"),
8574 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8575 m_FinishRouteOnKillFocus = true;
8576
8577 if (dlg_return == wxID_YES) {
8578 inserting = true; // part of the other route will be
8579 // preceeding the new route
8580 }
8581 }
8582 } else {
8583 wxString dmsg =
8584 _("Append last part of this route to the new route?");
8585 if (tail->GetIndexOf(pMousePoint) == 1)
8586 dmsg = _(
8587 "Append this route to the new route?"); // Picking the
8588 // first point
8589 // of another
8590 // route?
8591
8592 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8593 dlg_return = OCPNMessageBox(
8594 this, dmsg, _("OpenCPN Route Create"),
8595 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8596 m_FinishRouteOnKillFocus = true;
8597
8598 if (dlg_return == wxID_YES) {
8599 appending = true; // part of the other route will be
8600 // appended to the new route
8601 }
8602 }
8603 }
8604 }
8605
8606 // check all other routes to see if this point appears in any
8607 // other route If it appears in NO other route, then it should e
8608 // considered an isolated mark
8609 if (!FindRouteContainingWaypoint(pMousePoint))
8610 pMousePoint->SetShared(true);
8611 }
8612 }
8613 }
8614
8615 if (NULL == pMousePoint) { // need a new point
8616 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8617 "", wxEmptyString);
8618 pMousePoint->SetNameShown(false);
8619
8620 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8621
8622 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8623
8624 if (m_routeState > 1)
8625 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8626 Undo_IsOrphanded, NULL);
8627 }
8628
8629 if (m_pMouseRoute) {
8630 if (m_routeState == 1) {
8631 // First point in the route.
8632 m_pMouseRoute->AddPoint(pMousePoint);
8633 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8634 } else {
8635 if (m_pMouseRoute->m_NextLegGreatCircle) {
8636 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8637 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8638 &rhumbBearing, &rhumbDist);
8639 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8640 rlat, &gcDist, &gcBearing, NULL);
8641 double gcDistNM = gcDist / 1852.0;
8642
8643 // Empirically found expression to get reasonable route segments.
8644 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8645 pow(rhumbDist - gcDistNM - 1, 0.5);
8646
8647 wxString msg;
8648 msg << _("For this leg the Great Circle route is ")
8649 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8650 << _(" shorter than rhumbline.\n\n")
8651 << _("Would you like include the Great Circle routing points "
8652 "for this leg?");
8653
8654 m_FinishRouteOnKillFocus = false;
8655 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8656 // does not fully capture mouse
8657
8658 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8659 wxYES_NO | wxNO_DEFAULT);
8660
8661 m_disable_edge_pan = false;
8662 m_FinishRouteOnKillFocus = true;
8663
8664 if (answer == wxID_YES) {
8665 RoutePoint *gcPoint;
8666 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8667 wxRealPoint gcCoord;
8668
8669 for (int i = 1; i <= segmentCount; i++) {
8670 double fraction = (double)i * (1.0 / (double)segmentCount);
8671 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8672 gcDist * fraction, gcBearing,
8673 &gcCoord.x, &gcCoord.y, NULL);
8674
8675 if (i < segmentCount) {
8676 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8677 wxEmptyString);
8678 gcPoint->SetNameShown(false);
8679 // pConfig->AddNewWayPoint(gcPoint, -1);
8680 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8681
8682 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8683 gcPoint);
8684 } else {
8685 gcPoint = pMousePoint; // Last point, previously exsisting!
8686 }
8687
8688 m_pMouseRoute->AddPoint(gcPoint);
8689 pSelect->AddSelectableRouteSegment(
8690 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8691 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8692 prevGcPoint = gcPoint;
8693 }
8694
8695 undo->CancelUndoableAction(true);
8696
8697 } else {
8698 m_pMouseRoute->AddPoint(pMousePoint);
8699 pSelect->AddSelectableRouteSegment(
8700 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8701 pMousePoint, m_pMouseRoute);
8702 undo->AfterUndoableAction(m_pMouseRoute);
8703 }
8704 } else {
8705 // Ordinary rhumblinesegment.
8706 m_pMouseRoute->AddPoint(pMousePoint);
8707 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8708 rlon, m_prev_pMousePoint,
8709 pMousePoint, m_pMouseRoute);
8710 undo->AfterUndoableAction(m_pMouseRoute);
8711 }
8712 }
8713 }
8714 m_prev_rlat = rlat;
8715 m_prev_rlon = rlon;
8716 m_prev_pMousePoint = pMousePoint;
8717 if (m_pMouseRoute)
8718 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8719
8720 m_routeState++;
8721
8722 if (appending ||
8723 inserting) { // Appending a route or making a new route
8724 int connect = tail->GetIndexOf(pMousePoint);
8725 if (connect == 1) {
8726 inserting = false; // there is nothing to insert
8727 appending = true; // so append
8728 }
8729 int length = tail->GetnPoints();
8730
8731 int i;
8732 int start, stop;
8733 if (appending) {
8734 start = connect + 1;
8735 stop = length;
8736 } else { // inserting
8737 start = 1;
8738 stop = connect;
8739 m_pMouseRoute->RemovePoint(
8740 m_pMouseRoute
8741 ->GetLastPoint()); // Remove the first and only point
8742 }
8743 for (i = start; i <= stop; i++) {
8744 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8745 if (m_pMouseRoute)
8746 m_pMouseRoute->m_lastMousePointIndex =
8747 m_pMouseRoute->GetnPoints();
8748 m_routeState++;
8749 gFrame->RefreshAllCanvas();
8750 ret = true;
8751 }
8752 m_prev_rlat =
8753 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8754 m_prev_rlon =
8755 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8756 m_pMouseRoute->FinalizeForRendering();
8757 }
8758 gFrame->RefreshAllCanvas();
8759 ret = true;
8760 }
8761
8762 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8763 {
8764 SetCursor(*pCursorPencil);
8765
8766 if (!m_pMeasureRoute) {
8767 m_pMeasureRoute = new Route();
8768 pRouteList->push_back(m_pMeasureRoute);
8769 }
8770
8771 if (m_nMeasureState == 1) {
8772 r_rband.x = x;
8773 r_rband.y = y;
8774 }
8775
8776 RoutePoint *pMousePoint =
8777 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8778 wxEmptyString, wxEmptyString);
8779 pMousePoint->m_bShowName = false;
8780 pMousePoint->SetShowWaypointRangeRings(false);
8781
8782 m_pMeasureRoute->AddPoint(pMousePoint);
8783
8784 m_prev_rlat = m_cursor_lat;
8785 m_prev_rlon = m_cursor_lon;
8786 m_prev_pMousePoint = pMousePoint;
8787 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8788
8789 m_nMeasureState++;
8790 gFrame->RefreshAllCanvas();
8791 ret = true;
8792 }
8793
8794 else {
8795 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8796 }
8797 } // !g_btouch
8798 else { // g_btouch
8799 m_last_touch_down_pos = event.GetPosition();
8800
8801 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8802 // if near screen edge, pan with injection
8803 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8804 // return;
8805 // }
8806 }
8807 }
8808
8809 if (ret) return true;
8810 }
8811
8812 if (event.Dragging()) {
8813 // in touch screen mode ensure the finger/cursor is on the selected point's
8814 // radius to allow dragging
8815 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8816 if (g_btouch) {
8817 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8818 SelectItem *pFind = NULL;
8819 SelectableItemList SelList = pSelect->FindSelectionList(
8820 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8821 for (SelectItem *pFind : SelList) {
8822 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8823 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8824 }
8825 }
8826
8827 // Check for use of dragHandle
8828 if (m_pRoutePointEditTarget &&
8829 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8830 SelectItem *pFind = NULL;
8831 SelectableItemList SelList = pSelect->FindSelectionList(
8832 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8833 for (SelectItem *pFind : SelList) {
8834 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8835 if (m_pRoutePointEditTarget == frp) {
8836 m_bIsInRadius = true;
8837 break;
8838 }
8839 }
8840
8841 if (!m_dragoffsetSet) {
8842 RoutePointGui(*m_pRoutePointEditTarget)
8843 .PresetDragOffset(this, mouse_x, mouse_y);
8844 m_dragoffsetSet = true;
8845 }
8846 }
8847 }
8848
8849 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8850 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8851
8852 if (NULL == g_pMarkInfoDialog) {
8853 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8854 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8855 DraggingAllowed = false;
8856
8857 if (m_pRoutePointEditTarget &&
8858 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8859 DraggingAllowed = false;
8860
8861 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8862
8863 if (DraggingAllowed) {
8864 if (!undo->InUndoableAction()) {
8865 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8866 Undo_NeedsCopy, m_pFoundPoint);
8867 }
8868
8869 // Get the update rectangle for the union of the un-edited routes
8870 wxRect pre_rect;
8871
8872 if (!g_bopengl && m_pEditRouteArray) {
8873 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8874 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8875 // Need to validate route pointer
8876 // Route may be gone due to drgging close to ownship with
8877 // "Delete On Arrival" state set, as in the case of
8878 // navigating to an isolated waypoint on a temporary route
8879 if (g_pRouteMan->IsRouteValid(pr)) {
8880 wxRect route_rect;
8881 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8882 pre_rect.Union(route_rect);
8883 }
8884 }
8885 }
8886
8887 double new_cursor_lat = m_cursor_lat;
8888 double new_cursor_lon = m_cursor_lon;
8889
8890 if (CheckEdgePan(x, y, true, 5, 2))
8891 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8892
8893 // update the point itself
8894 if (g_btouch) {
8895 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8896 // new_cursor_lat, new_cursor_lon);
8897 RoutePointGui(*m_pRoutePointEditTarget)
8898 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8899 // update the Drag Handle entry in the pSelect list
8900 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8901 m_pRoutePointEditTarget,
8902 SELTYPE_DRAGHANDLE);
8903 m_pFoundPoint->m_slat =
8904 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8905 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8906 } else {
8907 m_pRoutePointEditTarget->m_lat =
8908 new_cursor_lat; // update the RoutePoint entry
8909 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8910 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8911 m_pFoundPoint->m_slat =
8912 new_cursor_lat; // update the SelectList entry
8913 m_pFoundPoint->m_slon = new_cursor_lon;
8914 }
8915
8916 // Update the MarkProperties Dialog, if currently shown
8917 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8918 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8919 g_pMarkInfoDialog->UpdateProperties(true);
8920 }
8921
8922 if (g_bopengl) {
8923 // InvalidateGL();
8924 Refresh(false);
8925 } else {
8926 // Get the update rectangle for the edited route
8927 wxRect post_rect;
8928
8929 if (m_pEditRouteArray) {
8930 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8931 ir++) {
8932 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8933 if (g_pRouteMan->IsRouteValid(pr)) {
8934 wxRect route_rect;
8935 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8936 post_rect.Union(route_rect);
8937 }
8938 }
8939 }
8940
8941 // Invalidate the union region
8942 pre_rect.Union(post_rect);
8943 RefreshRect(pre_rect, false);
8944 }
8945 gFrame->RefreshCanvasOther(this);
8946 m_bRoutePoinDragging = true;
8947 }
8948 ret = true;
8949 } // if Route Editing
8950
8951 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8952 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8953
8954 if (NULL == g_pMarkInfoDialog) {
8955 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8956 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8957 DraggingAllowed = false;
8958
8959 if (m_pRoutePointEditTarget &&
8960 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8961 DraggingAllowed = false;
8962
8963 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8964
8965 if (DraggingAllowed) {
8966 if (!undo->InUndoableAction()) {
8967 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8968 Undo_NeedsCopy, m_pFoundPoint);
8969 }
8970
8971 // The mark may be an anchorwatch
8972 double lpp1 = 0.;
8973 double lpp2 = 0.;
8974 double lppmax;
8975
8976 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8977 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8978 }
8979 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8980 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8981 }
8982 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8983
8984 // Get the update rectangle for the un-edited mark
8985 wxRect pre_rect;
8986 if (!g_bopengl) {
8987 RoutePointGui(*m_pRoutePointEditTarget)
8988 .CalculateDCRect(m_dc_route, this, &pre_rect);
8989 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8990 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8991 (int)(lppmax - (pre_rect.height / 2)));
8992 }
8993
8994 // update the point itself
8995 if (g_btouch) {
8996 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8997 // m_cursor_lat, m_cursor_lon);
8998 RoutePointGui(*m_pRoutePointEditTarget)
8999 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9000 // update the Drag Handle entry in the pSelect list
9001 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9002 m_pRoutePointEditTarget,
9003 SELTYPE_DRAGHANDLE);
9004 m_pFoundPoint->m_slat =
9005 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9006 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9007 } else {
9008 m_pRoutePointEditTarget->m_lat =
9009 m_cursor_lat; // update the RoutePoint entry
9010 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9011 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9012 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9013 m_pFoundPoint->m_slon = m_cursor_lon;
9014 }
9015
9016 // Update the MarkProperties Dialog, if currently shown
9017 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9018 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9019 g_pMarkInfoDialog->UpdateProperties(true);
9020 }
9021
9022 // Invalidate the union region
9023 if (g_bopengl) {
9024 if (!g_btouch) InvalidateGL();
9025 Refresh(false);
9026 } else {
9027 // Get the update rectangle for the edited mark
9028 wxRect post_rect;
9029 RoutePointGui(*m_pRoutePointEditTarget)
9030 .CalculateDCRect(m_dc_route, this, &post_rect);
9031 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9032 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9033 (int)(lppmax - (post_rect.height / 2)));
9034
9035 // Invalidate the union region
9036 pre_rect.Union(post_rect);
9037 RefreshRect(pre_rect, false);
9038 }
9039 gFrame->RefreshCanvasOther(this);
9040 m_bRoutePoinDragging = true;
9041 }
9042 ret = g_btouch ? m_bRoutePoinDragging : true;
9043 }
9044
9045 if (ret) return true;
9046 } // dragging
9047
9048 if (event.LeftUp()) {
9049 bool b_startedit_route = false;
9050 m_dragoffsetSet = false;
9051
9052 if (g_btouch) {
9053 m_bChartDragging = false;
9054 m_bIsInRadius = false;
9055
9056 if (m_routeState) // creating route?
9057 {
9058 if (m_ignore_next_leftup) {
9059 m_ignore_next_leftup = false;
9060 return false;
9061 }
9062
9063 if (m_bedge_pan) {
9064 m_bedge_pan = false;
9065 return false;
9066 }
9067
9068 double rlat, rlon;
9069 bool appending = false;
9070 bool inserting = false;
9071 Route *tail = 0;
9072
9073 rlat = m_cursor_lat;
9074 rlon = m_cursor_lon;
9075
9076 if (m_pRoutePointEditTarget) {
9077 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9078 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9079 if (!g_bopengl) {
9080 wxRect wp_rect;
9081 RoutePointGui(*m_pRoutePointEditTarget)
9082 .CalculateDCRect(m_dc_route, this, &wp_rect);
9083 RefreshRect(wp_rect, true);
9084 }
9085 m_pRoutePointEditTarget = NULL;
9086 }
9087 m_bRouteEditing = true;
9088
9089 if (m_routeState == 1) {
9090 m_pMouseRoute = new Route();
9091 m_pMouseRoute->SetHiLite(50);
9092 pRouteList->push_back(m_pMouseRoute);
9093 r_rband.x = x;
9094 r_rband.y = y;
9095 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9096 }
9097
9098 // Check to see if there is a nearby point which may be reused
9099 RoutePoint *pMousePoint = NULL;
9100
9101 // Calculate meaningful SelectRadius
9102 double nearby_radius_meters =
9103 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9104
9105 RoutePoint *pNearbyPoint =
9106 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9107 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9108 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9109 int dlg_return;
9110#ifndef __WXOSX__
9111 m_FinishRouteOnKillFocus =
9112 false; // Avoid route finish on focus change for message dialog
9113 dlg_return = OCPNMessageBox(
9114 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9115 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9116 m_FinishRouteOnKillFocus = true;
9117#else
9118 dlg_return = wxID_YES;
9119#endif
9120 if (dlg_return == wxID_YES) {
9121 pMousePoint = pNearbyPoint;
9122
9123 // Using existing waypoint, so nothing to delete for undo.
9124 if (m_routeState > 1)
9125 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9126 Undo_HasParent, NULL);
9127 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9128
9129 bool procede = false;
9130 if (tail) {
9131 procede = true;
9132 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9133 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9134 procede = false;
9135 }
9136
9137 if (procede) {
9138 int dlg_return;
9139 m_FinishRouteOnKillFocus = false;
9140 if (m_routeState == 1) { // first point in new route, preceeding
9141 // route to be added? touch case
9142
9143 wxString dmsg =
9144 _("Insert first part of this route in the new route?");
9145 if (tail->GetIndexOf(pMousePoint) ==
9146 tail->GetnPoints()) // Starting on last point of another
9147 // route?
9148 dmsg = _("Insert this route in the new route?");
9149
9150 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9151 dlg_return =
9152 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9153 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9154 m_FinishRouteOnKillFocus = true;
9155
9156 if (dlg_return == wxID_YES) {
9157 inserting = true; // part of the other route will be
9158 // preceeding the new route
9159 }
9160 }
9161 } else {
9162 wxString dmsg =
9163 _("Append last part of this route to the new route?");
9164 if (tail->GetIndexOf(pMousePoint) == 1)
9165 dmsg = _(
9166 "Append this route to the new route?"); // Picking the
9167 // first point of
9168 // another route?
9169
9170 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9171 dlg_return =
9172 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9173 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9174 m_FinishRouteOnKillFocus = true;
9175
9176 if (dlg_return == wxID_YES) {
9177 appending = true; // part of the other route will be
9178 // appended to the new route
9179 }
9180 }
9181 }
9182 }
9183
9184 // check all other routes to see if this point appears in any other
9185 // route If it appears in NO other route, then it should e
9186 // considered an isolated mark
9187 if (!FindRouteContainingWaypoint(pMousePoint))
9188 pMousePoint->SetShared(true);
9189 }
9190 }
9191
9192 if (NULL == pMousePoint) { // need a new point
9193 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9194 "", wxEmptyString);
9195 pMousePoint->SetNameShown(false);
9196
9197 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9198
9199 if (m_routeState > 1)
9200 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9201 Undo_IsOrphanded, NULL);
9202 }
9203
9204 if (m_routeState == 1) {
9205 // First point in the route.
9206 m_pMouseRoute->AddPoint(pMousePoint);
9207 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9208
9209 } else {
9210 if (m_pMouseRoute->m_NextLegGreatCircle) {
9211 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9212 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9213 &rhumbBearing, &rhumbDist);
9214 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9215 &gcDist, &gcBearing, NULL);
9216 double gcDistNM = gcDist / 1852.0;
9217
9218 // Empirically found expression to get reasonable route segments.
9219 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9220 pow(rhumbDist - gcDistNM - 1, 0.5);
9221
9222 wxString msg;
9223 msg << _("For this leg the Great Circle route is ")
9224 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9225 << _(" shorter than rhumbline.\n\n")
9226 << _("Would you like include the Great Circle routing points "
9227 "for this leg?");
9228
9229#ifndef __WXOSX__
9230 m_FinishRouteOnKillFocus = false;
9231 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9232 wxYES_NO | wxNO_DEFAULT);
9233 m_FinishRouteOnKillFocus = true;
9234#else
9235 int answer = wxID_NO;
9236#endif
9237
9238 if (answer == wxID_YES) {
9239 RoutePoint *gcPoint;
9240 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9241 wxRealPoint gcCoord;
9242
9243 for (int i = 1; i <= segmentCount; i++) {
9244 double fraction = (double)i * (1.0 / (double)segmentCount);
9245 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9246 gcDist * fraction, gcBearing,
9247 &gcCoord.x, &gcCoord.y, NULL);
9248
9249 if (i < segmentCount) {
9250 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9251 wxEmptyString);
9252 gcPoint->SetNameShown(false);
9253 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9254 gcPoint);
9255 } else {
9256 gcPoint = pMousePoint; // Last point, previously exsisting!
9257 }
9258
9259 m_pMouseRoute->AddPoint(gcPoint);
9260 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9261
9262 pSelect->AddSelectableRouteSegment(
9263 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9264 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9265 prevGcPoint = gcPoint;
9266 }
9267
9268 undo->CancelUndoableAction(true);
9269
9270 } else {
9271 m_pMouseRoute->AddPoint(pMousePoint);
9272 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9273 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9274 rlon, m_prev_pMousePoint,
9275 pMousePoint, m_pMouseRoute);
9276 undo->AfterUndoableAction(m_pMouseRoute);
9277 }
9278 } else {
9279 // Ordinary rhumblinesegment.
9280 m_pMouseRoute->AddPoint(pMousePoint);
9281 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9282
9283 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9284 rlon, m_prev_pMousePoint,
9285 pMousePoint, m_pMouseRoute);
9286 undo->AfterUndoableAction(m_pMouseRoute);
9287 }
9288 }
9289
9290 m_prev_rlat = rlat;
9291 m_prev_rlon = rlon;
9292 m_prev_pMousePoint = pMousePoint;
9293 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9294
9295 m_routeState++;
9296
9297 if (appending ||
9298 inserting) { // Appending a route or making a new route
9299 int connect = tail->GetIndexOf(pMousePoint);
9300 if (connect == 1) {
9301 inserting = false; // there is nothing to insert
9302 appending = true; // so append
9303 }
9304 int length = tail->GetnPoints();
9305
9306 int i;
9307 int start, stop;
9308 if (appending) {
9309 start = connect + 1;
9310 stop = length;
9311 } else { // inserting
9312 start = 1;
9313 stop = connect;
9314 m_pMouseRoute->RemovePoint(
9315 m_pMouseRoute
9316 ->GetLastPoint()); // Remove the first and only point
9317 }
9318 for (i = start; i <= stop; i++) {
9319 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9320 if (m_pMouseRoute)
9321 m_pMouseRoute->m_lastMousePointIndex =
9322 m_pMouseRoute->GetnPoints();
9323 m_routeState++;
9324 gFrame->RefreshAllCanvas();
9325 ret = true;
9326 }
9327 m_prev_rlat =
9328 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9329 m_prev_rlon =
9330 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9331 m_pMouseRoute->FinalizeForRendering();
9332 }
9333
9334 Refresh(true);
9335 ret = true;
9336 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9337 {
9338 if (m_bedge_pan) {
9339 m_bedge_pan = false;
9340 return false;
9341 }
9342
9343 if (m_ignore_next_leftup) {
9344 m_ignore_next_leftup = false;
9345 return false;
9346 }
9347
9348 if (m_nMeasureState == 1) {
9349 m_pMeasureRoute = new Route();
9350 pRouteList->push_back(m_pMeasureRoute);
9351 r_rband.x = x;
9352 r_rband.y = y;
9353 }
9354
9355 if (m_pMeasureRoute) {
9356 RoutePoint *pMousePoint =
9357 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9358 wxEmptyString, wxEmptyString);
9359 pMousePoint->m_bShowName = false;
9360
9361 m_pMeasureRoute->AddPoint(pMousePoint);
9362
9363 m_prev_rlat = m_cursor_lat;
9364 m_prev_rlon = m_cursor_lon;
9365 m_prev_pMousePoint = pMousePoint;
9366 m_pMeasureRoute->m_lastMousePointIndex =
9367 m_pMeasureRoute->GetnPoints();
9368
9369 m_nMeasureState++;
9370 } else {
9371 CancelMeasureRoute();
9372 }
9373
9374 Refresh(true);
9375 ret = true;
9376 } else {
9377 bool bSelectAllowed = true;
9378 if (NULL == g_pMarkInfoDialog) {
9379 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9380 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9381 bSelectAllowed = false;
9382
9383 // Avoid accidental selection of routepoint if last touchdown started
9384 // a significant chart drag operation
9385 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9386 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9387 significant_drag) ||
9388 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9389 significant_drag)) {
9390 bSelectAllowed = false;
9391 }
9392
9393 /*if this left up happens at the end of a route point dragging and if
9394 the cursor/thumb is on the draghandle icon, not on the point iself a new
9395 selection will select nothing and the drag will never be ended, so the
9396 legs around this point never selectable. At this step we don't need a
9397 new selection, just keep the previoulsly selected and dragged point */
9398 if (m_bRoutePoinDragging) bSelectAllowed = false;
9399
9400 if (bSelectAllowed) {
9401 bool b_was_editing_mark = m_bMarkEditing;
9402 bool b_was_editing_route = m_bRouteEditing;
9403 FindRoutePointsAtCursor(SelectRadius,
9404 true); // Possibly selecting a point in a
9405 // route for later dragging
9406
9407 /*route and a mark points in layer can't be dragged so should't be
9408 * selected and no draghandle icon*/
9409 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9410 m_pRoutePointEditTarget = NULL;
9411
9412 if (!b_was_editing_route) {
9413 if (m_pEditRouteArray) {
9414 b_startedit_route = true;
9415
9416 // Hide the track and route rollover during route point edit, not
9417 // needed, and may be confusing
9418 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9419 m_pTrackRolloverWin->IsActive(false);
9420 }
9421 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9422 m_pRouteRolloverWin->IsActive(false);
9423 }
9424
9425 wxRect pre_rect;
9426 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9427 ir++) {
9428 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9429 // Need to validate route pointer
9430 // Route may be gone due to drgging close to ownship with
9431 // "Delete On Arrival" state set, as in the case of
9432 // navigating to an isolated waypoint on a temporary route
9433 if (g_pRouteMan->IsRouteValid(pr)) {
9434 // pr->SetHiLite(50);
9435 wxRect route_rect;
9436 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9437 pre_rect.Union(route_rect);
9438 }
9439 }
9440 RefreshRect(pre_rect, true);
9441 }
9442 } else {
9443 b_startedit_route = false;
9444 }
9445
9446 // Mark editing in touch mode, left-up event.
9447 if (m_pRoutePointEditTarget) {
9448 if (b_was_editing_mark ||
9449 b_was_editing_route) { // kill previous hilight
9450 if (m_lastRoutePointEditTarget) {
9451 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9452 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9453 RoutePointGui(*m_lastRoutePointEditTarget)
9454 .EnableDragHandle(false);
9455 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9456 SELTYPE_DRAGHANDLE);
9457 }
9458 }
9459
9460 if (m_pRoutePointEditTarget) {
9461 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9462 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9463 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9464 wxPoint2DDouble dragHandlePoint =
9465 RoutePointGui(*m_pRoutePointEditTarget)
9466 .GetDragHandlePoint(this);
9467 pSelect->AddSelectablePoint(
9468 dragHandlePoint.m_y, dragHandlePoint.m_x,
9469 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9470 }
9471 } else { // Deselect everything
9472 if (m_lastRoutePointEditTarget) {
9473 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9474 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9475 RoutePointGui(*m_lastRoutePointEditTarget)
9476 .EnableDragHandle(false);
9477 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9478 SELTYPE_DRAGHANDLE);
9479
9480 // Clear any routes being edited, probably orphans
9481 wxArrayPtrVoid *lastEditRouteArray =
9483 m_lastRoutePointEditTarget);
9484 if (lastEditRouteArray) {
9485 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9486 ir++) {
9487 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9488 if (g_pRouteMan->IsRouteValid(pr)) {
9489 pr->m_bIsBeingEdited = false;
9490 }
9491 }
9492 delete lastEditRouteArray;
9493 }
9494 }
9495 }
9496
9497 // Do the refresh
9498
9499 if (g_bopengl) {
9500 InvalidateGL();
9501 Refresh(false);
9502 } else {
9503 if (m_lastRoutePointEditTarget) {
9504 wxRect wp_rect;
9505 RoutePointGui(*m_lastRoutePointEditTarget)
9506 .CalculateDCRect(m_dc_route, this, &wp_rect);
9507 RefreshRect(wp_rect, true);
9508 }
9509
9510 if (m_pRoutePointEditTarget) {
9511 wxRect wp_rect;
9512 RoutePointGui(*m_pRoutePointEditTarget)
9513 .CalculateDCRect(m_dc_route, this, &wp_rect);
9514 RefreshRect(wp_rect, true);
9515 }
9516 }
9517 }
9518 } // bSelectAllowed
9519
9520 // Check to see if there is a route or AIS target under the cursor
9521 // If so, start the rollover timer which creates the popup
9522 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9523 bool b_start_rollover = false;
9524 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9525 SelectItem *pFind = pSelectAIS->FindSelection(
9526 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9527 if (pFind) b_start_rollover = true;
9528 }
9529
9530 if (!b_start_rollover && !b_startedit_route) {
9531 SelectableItemList SelList = pSelect->FindSelectionList(
9532 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9533 for (SelectItem *pFindSel : SelList) {
9534 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9535 if (pr && pr->IsVisible()) {
9536 b_start_rollover = true;
9537 break;
9538 }
9539 } // while
9540 }
9541
9542 if (!b_start_rollover && !b_startedit_route) {
9543 SelectableItemList SelList = pSelect->FindSelectionList(
9544 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9545 for (SelectItem *pFindSel : SelList) {
9546 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9547 if (tr && tr->IsVisible()) {
9548 b_start_rollover = true;
9549 break;
9550 }
9551 } // while
9552 }
9553
9554 if (b_start_rollover)
9555 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9556 wxTIMER_ONE_SHOT);
9557 Route *tail = 0;
9558 Route *current = 0;
9559 bool appending = false;
9560 bool inserting = false;
9561 int connect = 0;
9562 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9563 // drag
9564 if (m_pRoutePointEditTarget) {
9565 // Check to see if there is a nearby point which may replace the
9566 // dragged one
9567 RoutePoint *pMousePoint = NULL;
9568
9569 int index_last;
9570 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9571 double nearby_radius_meters =
9572 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9573 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9574 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9575 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9576 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9577 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9578 bool duplicate =
9579 false; // ensure we won't create duplicate point in routes
9580 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9581 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9582 ir++) {
9583 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9584 if (pr && pr->pRoutePointList) {
9585 auto *list = pr->pRoutePointList;
9586 auto pos =
9587 std::find(list->begin(), list->end(), pNearbyPoint);
9588 if (pos != list->end()) {
9589 duplicate = true;
9590 break;
9591 }
9592 }
9593 }
9594 }
9595
9596 // Special case:
9597 // Allow "re-use" of a route's waypoints iff it is a simple
9598 // isolated route. This allows, for instance, creation of a closed
9599 // polygon route
9600 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9601
9602 if (!duplicate) {
9603 int dlg_return;
9604 dlg_return =
9605 OCPNMessageBox(this,
9606 _("Replace this RoutePoint by the nearby "
9607 "Waypoint?"),
9608 _("OpenCPN RoutePoint change"),
9609 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9610 if (dlg_return == wxID_YES) {
9611 /*double confirmation if the dragged point has been manually
9612 * created which can be important and could be deleted
9613 * unintentionally*/
9614
9615 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9616 pNearbyPoint);
9617 current =
9618 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9619
9620 if (tail && current && (tail != current)) {
9621 int dlg_return1;
9622 connect = tail->GetIndexOf(pNearbyPoint);
9623 int index_current_route =
9624 current->GetIndexOf(m_pRoutePointEditTarget);
9625 index_last = current->GetIndexOf(current->GetLastPoint());
9626 dlg_return1 = wxID_NO;
9627 if (index_last ==
9628 index_current_route) { // we are dragging the last
9629 // point of the route
9630 if (connect != tail->GetnPoints()) { // anything to do?
9631
9632 wxString dmsg(
9633 _("Last part of route to be appended to dragged "
9634 "route?"));
9635 if (connect == 1)
9636 dmsg =
9637 _("Full route to be appended to dragged route?");
9638
9639 dlg_return1 = OCPNMessageBox(
9640 this, dmsg, _("OpenCPN Route Create"),
9641 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9642 if (dlg_return1 == wxID_YES) {
9643 appending = true;
9644 }
9645 }
9646 } else if (index_current_route ==
9647 1) { // dragging the first point of the route
9648 if (connect != 1) { // anything to do?
9649
9650 wxString dmsg(
9651 _("First part of route to be inserted into dragged "
9652 "route?"));
9653 if (connect == tail->GetnPoints())
9654 dmsg = _(
9655 "Full route to be inserted into dragged route?");
9656
9657 dlg_return1 = OCPNMessageBox(
9658 this, dmsg, _("OpenCPN Route Create"),
9659 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9660 if (dlg_return1 == wxID_YES) {
9661 inserting = true;
9662 }
9663 }
9664 }
9665 }
9666
9667 if (m_pRoutePointEditTarget->IsShared()) {
9668 // dlg_return = wxID_NO;
9669 dlg_return = OCPNMessageBox(
9670 this,
9671 _("Do you really want to delete and replace this "
9672 "WayPoint") +
9673 "\n" + _("which has been created manually?"),
9674 ("OpenCPN RoutePoint warning"),
9675 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9676 }
9677 }
9678 if (dlg_return == wxID_YES) {
9679 pMousePoint = pNearbyPoint;
9680 if (pMousePoint->m_bIsolatedMark) {
9681 pMousePoint->SetShared(true);
9682 }
9683 pMousePoint->m_bIsolatedMark =
9684 false; // definitely no longer isolated
9685 pMousePoint->m_bIsInRoute = true;
9686 }
9687 }
9688 }
9689 }
9690 if (!pMousePoint)
9691 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9692
9693 if (m_pEditRouteArray) {
9694 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9695 ir++) {
9696 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9697 if (g_pRouteMan->IsRouteValid(pr)) {
9698 if (pMousePoint) { // remove the dragged point and insert the
9699 // nearby
9700 auto *list = pr->pRoutePointList;
9701 auto pos = std::find(list->begin(), list->end(),
9702 m_pRoutePointEditTarget);
9703
9704 pSelect->DeleteAllSelectableRoutePoints(pr);
9705 pSelect->DeleteAllSelectableRouteSegments(pr);
9706
9707 pr->pRoutePointList->insert(pos, pMousePoint);
9708 pos = std::find(list->begin(), list->end(),
9709 m_pRoutePointEditTarget);
9710 pr->pRoutePointList->erase(pos);
9711
9712 pSelect->AddAllSelectableRouteSegments(pr);
9713 pSelect->AddAllSelectableRoutePoints(pr);
9714 }
9715 pr->FinalizeForRendering();
9716 pr->UpdateSegmentDistances();
9717 if (m_bRoutePoinDragging) {
9718 // pConfig->UpdateRoute(pr);
9719 NavObj_dB::GetInstance().UpdateRoute(pr);
9720 }
9721 }
9722 }
9723 }
9724
9725 // Update the RouteProperties Dialog, if currently shown
9726 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9727 if (m_pEditRouteArray) {
9728 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9729 ir++) {
9730 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9731 if (g_pRouteMan->IsRouteValid(pr)) {
9732 if (pRoutePropDialog->GetRoute() == pr) {
9733 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9734 }
9735 /* cannot edit track points anyway
9736 else if ( ( NULL !=
9737 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9738 pTrackPropDialog->m_pTrack == pr ) {
9739 pTrackPropDialog->SetTrackAndUpdate(
9740 pr );
9741 }
9742 */
9743 }
9744 }
9745 }
9746 }
9747 if (pMousePoint) { // clear all about the dragged point
9748 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9749 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9750 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9751 // Hide mark properties dialog if open on the replaced point
9752 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9753 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9754 g_pMarkInfoDialog->Hide();
9755
9756 delete m_pRoutePointEditTarget;
9757 m_lastRoutePointEditTarget = NULL;
9758 m_pRoutePointEditTarget = NULL;
9759 undo->AfterUndoableAction(pMousePoint);
9760 undo->InvalidateUndo();
9761 }
9762 }
9763 }
9764
9765 else if (m_bMarkEditing) { // End of way point drag
9766 if (m_pRoutePointEditTarget)
9767 if (m_bRoutePoinDragging) {
9768 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9769 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9770 }
9771 }
9772
9773 if (m_pRoutePointEditTarget)
9774 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9775
9776 if (!m_pRoutePointEditTarget) {
9777 delete m_pEditRouteArray;
9778 m_pEditRouteArray = NULL;
9779 m_bRouteEditing = false;
9780 }
9781 m_bRoutePoinDragging = false;
9782
9783 if (appending) { // Appending to the route of which the last point is
9784 // dragged onto another route
9785
9786 // copy tail from connect until length to end of current after dragging
9787
9788 int length = tail->GetnPoints();
9789 for (int i = connect + 1; i <= length; i++) {
9790 current->AddPointAndSegment(tail->GetPoint(i), false);
9791 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9792 m_routeState++;
9793 gFrame->RefreshAllCanvas();
9794 ret = true;
9795 }
9796 current->FinalizeForRendering();
9797 current->m_bIsBeingEdited = false;
9798 FinishRoute();
9799 g_pRouteMan->DeleteRoute(tail);
9800 }
9801 if (inserting) {
9802 pSelect->DeleteAllSelectableRoutePoints(current);
9803 pSelect->DeleteAllSelectableRouteSegments(current);
9804 for (int i = 1; i < connect; i++) { // numbering in the tail route
9805 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9806 }
9807 pSelect->AddAllSelectableRouteSegments(current);
9808 pSelect->AddAllSelectableRoutePoints(current);
9809 current->FinalizeForRendering();
9810 current->m_bIsBeingEdited = false;
9811 g_pRouteMan->DeleteRoute(tail);
9812 }
9813
9814 // Update the RouteProperties Dialog, if currently shown
9815 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9816 if (m_pEditRouteArray) {
9817 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9818 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9819 if (g_pRouteMan->IsRouteValid(pr)) {
9820 if (pRoutePropDialog->GetRoute() == pr) {
9821 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9822 }
9823 }
9824 }
9825 }
9826 }
9827
9828 } // g_btouch
9829
9830 else { // !g_btouch
9831 if (m_bRouteEditing) { // End of RoutePoint drag
9832 Route *tail = 0;
9833 Route *current = 0;
9834 bool appending = false;
9835 bool inserting = false;
9836 int connect = 0;
9837 int index_last;
9838 if (m_pRoutePointEditTarget) {
9839 m_pRoutePointEditTarget->m_bBlink = false;
9840 // Check to see if there is a nearby point which may replace the
9841 // dragged one
9842 RoutePoint *pMousePoint = NULL;
9843 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9844 double nearby_radius_meters =
9845 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9846 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9847 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9848 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9849 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9850 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9851 bool duplicate = false; // don't create duplicate point in routes
9852 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9853 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9854 ir++) {
9855 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9856 if (pr && pr->pRoutePointList) {
9857 auto *list = pr->pRoutePointList;
9858 auto pos =
9859 std::find(list->begin(), list->end(), pNearbyPoint);
9860 if (pos != list->end()) {
9861 duplicate = true;
9862 break;
9863 }
9864 }
9865 }
9866 }
9867
9868 // Special case:
9869 // Allow "re-use" of a route's waypoints iff it is a simple
9870 // isolated route. This allows, for instance, creation of a closed
9871 // polygon route
9872 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9873
9874 if (!duplicate) {
9875 int dlg_return;
9876 dlg_return =
9877 OCPNMessageBox(this,
9878 _("Replace this RoutePoint by the nearby "
9879 "Waypoint?"),
9880 _("OpenCPN RoutePoint change"),
9881 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9882 if (dlg_return == wxID_YES) {
9883 /*double confirmation if the dragged point has been manually
9884 * created which can be important and could be deleted
9885 * unintentionally*/
9886 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9887 pNearbyPoint);
9888 current =
9889 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9890
9891 if (tail && current && (tail != current)) {
9892 int dlg_return1;
9893 connect = tail->GetIndexOf(pNearbyPoint);
9894 int index_current_route =
9895 current->GetIndexOf(m_pRoutePointEditTarget);
9896 index_last = current->GetIndexOf(current->GetLastPoint());
9897 dlg_return1 = wxID_NO;
9898 if (index_last ==
9899 index_current_route) { // we are dragging the last
9900 // point of the route
9901 if (connect != tail->GetnPoints()) { // anything to do?
9902
9903 wxString dmsg(
9904 _("Last part of route to be appended to dragged "
9905 "route?"));
9906 if (connect == 1)
9907 dmsg =
9908 _("Full route to be appended to dragged route?");
9909
9910 dlg_return1 = OCPNMessageBox(
9911 this, dmsg, _("OpenCPN Route Create"),
9912 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9913 if (dlg_return1 == wxID_YES) {
9914 appending = true;
9915 }
9916 }
9917 } else if (index_current_route ==
9918 1) { // dragging the first point of the route
9919 if (connect != 1) { // anything to do?
9920
9921 wxString dmsg(
9922 _("First part of route to be inserted into dragged "
9923 "route?"));
9924 if (connect == tail->GetnPoints())
9925 dmsg = _(
9926 "Full route to be inserted into dragged route?");
9927
9928 dlg_return1 = OCPNMessageBox(
9929 this, dmsg, _("OpenCPN Route Create"),
9930 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9931 if (dlg_return1 == wxID_YES) {
9932 inserting = true;
9933 }
9934 }
9935 }
9936 }
9937
9938 if (m_pRoutePointEditTarget->IsShared()) {
9939 dlg_return = wxID_NO;
9940 dlg_return = OCPNMessageBox(
9941 this,
9942 _("Do you really want to delete and replace this "
9943 "WayPoint") +
9944 "\n" + _("which has been created manually?"),
9945 ("OpenCPN RoutePoint warning"),
9946 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9947 }
9948 }
9949 if (dlg_return == wxID_YES) {
9950 pMousePoint = pNearbyPoint;
9951 if (pMousePoint->m_bIsolatedMark) {
9952 pMousePoint->SetShared(true);
9953 }
9954 pMousePoint->m_bIsolatedMark =
9955 false; // definitely no longer isolated
9956 pMousePoint->m_bIsInRoute = true;
9957 }
9958 }
9959 }
9960 }
9961 if (!pMousePoint)
9962 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9963
9964 if (m_pEditRouteArray) {
9965 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9966 ir++) {
9967 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9968 if (g_pRouteMan->IsRouteValid(pr)) {
9969 if (pMousePoint) { // replace dragged point by nearby one
9970 auto *list = pr->pRoutePointList;
9971 auto pos = std::find(list->begin(), list->end(),
9972 m_pRoutePointEditTarget);
9973
9974 pSelect->DeleteAllSelectableRoutePoints(pr);
9975 pSelect->DeleteAllSelectableRouteSegments(pr);
9976
9977 pr->pRoutePointList->insert(pos, pMousePoint);
9978 pos = std::find(list->begin(), list->end(),
9979 m_pRoutePointEditTarget);
9980 if (pos != list->end()) list->erase(pos);
9981 // pr->pRoutePointList->erase(pos + 1);
9982
9983 pSelect->AddAllSelectableRouteSegments(pr);
9984 pSelect->AddAllSelectableRoutePoints(pr);
9985 }
9986 pr->FinalizeForRendering();
9987 pr->UpdateSegmentDistances();
9988 pr->m_bIsBeingEdited = false;
9989
9990 if (m_bRoutePoinDragging) {
9991 // Special case optimization.
9992 // Dragging a single point of a route
9993 // without any point additions or re-ordering
9994 if (!pMousePoint)
9995 NavObj_dB::GetInstance().UpdateRoutePoint(
9996 m_pRoutePointEditTarget);
9997 else
9998 NavObj_dB::GetInstance().UpdateRoute(pr);
9999 }
10000 pr->SetHiLite(0);
10001 }
10002 }
10003 Refresh(false);
10004 }
10005
10006 if (appending) {
10007 // copy tail from connect until length to end of current after
10008 // dragging
10009
10010 int length = tail->GetnPoints();
10011 for (int i = connect + 1; i <= length; i++) {
10012 current->AddPointAndSegment(tail->GetPoint(i), false);
10013 if (current)
10014 current->m_lastMousePointIndex = current->GetnPoints();
10015 m_routeState++;
10016 gFrame->RefreshAllCanvas();
10017 ret = true;
10018 }
10019 current->FinalizeForRendering();
10020 current->m_bIsBeingEdited = false;
10021 FinishRoute();
10022 g_pRouteMan->DeleteRoute(tail);
10023 }
10024 if (inserting) {
10025 pSelect->DeleteAllSelectableRoutePoints(current);
10026 pSelect->DeleteAllSelectableRouteSegments(current);
10027 for (int i = 1; i < connect; i++) { // numbering in the tail route
10028 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10029 }
10030 pSelect->AddAllSelectableRouteSegments(current);
10031 pSelect->AddAllSelectableRoutePoints(current);
10032 current->FinalizeForRendering();
10033 current->m_bIsBeingEdited = false;
10034 g_pRouteMan->DeleteRoute(tail);
10035 }
10036
10037 // Update the RouteProperties Dialog, if currently shown
10038 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10039 if (m_pEditRouteArray) {
10040 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10041 ir++) {
10042 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10043 if (g_pRouteMan->IsRouteValid(pr)) {
10044 if (pRoutePropDialog->GetRoute() == pr) {
10045 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10046 }
10047 }
10048 }
10049 }
10050 }
10051
10052 if (pMousePoint) {
10053 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10054 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10055 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10056 // Hide mark properties dialog if open on the replaced point
10057 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10058 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10059 g_pMarkInfoDialog->Hide();
10060
10061 delete m_pRoutePointEditTarget;
10062 m_lastRoutePointEditTarget = NULL;
10063 undo->AfterUndoableAction(pMousePoint);
10064 undo->InvalidateUndo();
10065 } else {
10066 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10067 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10068
10069 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10070 }
10071
10072 delete m_pEditRouteArray;
10073 m_pEditRouteArray = NULL;
10074 }
10075
10076 InvalidateGL();
10077 m_bRouteEditing = false;
10078 m_pRoutePointEditTarget = NULL;
10079
10080 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10081 ret = true;
10082 }
10083
10084 else if (m_bMarkEditing) { // end of Waypoint drag
10085 if (m_pRoutePointEditTarget) {
10086 if (m_bRoutePoinDragging) {
10087 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10088 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10089 }
10090 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10091 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10092 if (!g_bopengl) {
10093 wxRect wp_rect;
10094 RoutePointGui(*m_pRoutePointEditTarget)
10095 .CalculateDCRect(m_dc_route, this, &wp_rect);
10096 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10097 RefreshRect(wp_rect, true);
10098 }
10099 }
10100 m_pRoutePointEditTarget = NULL;
10101 m_bMarkEditing = false;
10102 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10103 ret = true;
10104 }
10105
10106 else if (leftIsDown) { // left click for chart center
10107 leftIsDown = false;
10108 ret = false;
10109
10110 if (!g_btouch) {
10111 if (!m_bChartDragging && !m_bMeasure_Active) {
10112 } else {
10113 m_bChartDragging = false;
10114 }
10115 }
10116 }
10117 m_bRoutePoinDragging = false;
10118 } // !btouch
10119
10120 if (ret) return true;
10121 } // left up
10122
10123 if (event.RightDown()) {
10124 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10125 last_drag.x = mx;
10126 last_drag.y = my;
10127
10128 if (g_btouch) {
10129 // if( m_pRoutePointEditTarget )
10130 // return false;
10131 }
10132
10133 ret = true;
10134 m_FinishRouteOnKillFocus = false;
10135 CallPopupMenu(mx, my);
10136 m_FinishRouteOnKillFocus = true;
10137 } // Right down
10138
10139 return ret;
10140}
10141
10142bool panleftIsDown;
10143bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10144 // Skip all mouse processing if shift is held.
10145 // This allows plugins to implement shift+drag behaviors.
10146 if (event.ShiftDown()) {
10147 return false;
10148 }
10149 int x, y;
10150 event.GetPosition(&x, &y);
10151
10152 x *= m_displayScale;
10153 y *= m_displayScale;
10154
10155 // Check for wheel rotation
10156 // ideally, should be just longer than the time between
10157 // processing accumulated mouse events from the event queue
10158 // as would happen during screen redraws.
10159 int wheel_dir = event.GetWheelRotation();
10160
10161 if (wheel_dir) {
10162 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10163 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10164
10165 double factor = g_mouse_zoom_sensitivity;
10166 if (wheel_dir < 0) factor = 1 / factor;
10167
10168 if (g_bsmoothpanzoom) {
10169 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10170 if (wheel_dir == m_last_wheel_dir) {
10171 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10172 // m_zoom_target /= factor;
10173 } else
10174 StopMovement();
10175 } else {
10176 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10177 m_wheelstopwatch.Start(0);
10178 // m_zoom_target = VPoint.chart_scale / factor;
10179 }
10180 }
10181
10182 m_last_wheel_dir = wheel_dir;
10183
10184 ZoomCanvas(factor, true, false);
10185 }
10186
10187 if (event.LeftDown()) {
10188 // Skip the first left click if it will cause a canvas focus shift
10189 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10190 return false;
10191 }
10192
10193 last_drag.x = x, last_drag.y = y;
10194 panleftIsDown = true;
10195 }
10196
10197 if (event.LeftUp()) {
10198 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10199 // seen here.
10200 panleftIsDown = false;
10201
10202 if (!g_btouch) {
10203 if (!m_bChartDragging && !m_bMeasure_Active) {
10204 switch (cursor_region) {
10205 case MID_RIGHT: {
10206 PanCanvas(100, 0);
10207 break;
10208 }
10209
10210 case MID_LEFT: {
10211 PanCanvas(-100, 0);
10212 break;
10213 }
10214
10215 case MID_TOP: {
10216 PanCanvas(0, 100);
10217 break;
10218 }
10219
10220 case MID_BOT: {
10221 PanCanvas(0, -100);
10222 break;
10223 }
10224
10225 case CENTER: {
10226 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10227 break;
10228 }
10229 }
10230 } else {
10231 m_bChartDragging = false;
10232 }
10233 }
10234 }
10235 }
10236
10237 if (event.Dragging() && event.LeftIsDown()) {
10238 /*
10239 * fixed dragging.
10240 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10241 * before the drag event. Hence, as there is no mouse down event, last_drag
10242 * is not reset before the drag. And that results in one single drag
10243 * session, meaning you cannot drag the map a few miles north, lift your
10244 * finger, and the go even further north. Instead, the map resets itself
10245 * always to the very first drag start (since there is not reset of
10246 * last_drag).
10247 *
10248 * Besides, should not left down and dragging be enough of a situation to
10249 * start a drag procedure?
10250 *
10251 * Anyways, guarded it to be active in touch situations only.
10252 */
10253 if (g_btouch && !m_inPinch) {
10254 struct timespec now;
10255 clock_gettime(CLOCK_MONOTONIC, &now);
10256 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10257
10258 bool trigger_hold = false;
10259 if (false == m_bChartDragging) {
10260 if (m_DragTrigger < 0) {
10261 // printf("\ntrigger1\n");
10262 m_DragTrigger = 0;
10263 m_DragTriggerStartTime = tnow;
10264 trigger_hold = true;
10265 } else {
10266 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10267 m_DragTrigger = -1; // Reset trigger
10268 // printf("trigger fired\n");
10269 }
10270 }
10271 }
10272 if (trigger_hold) return true;
10273
10274 if (false == m_bChartDragging) {
10275 // printf("starting drag\n");
10276 // Reset drag calculation members
10277 last_drag.x = x - 1, last_drag.y = y - 1;
10278 m_bChartDragging = true;
10279 m_chart_drag_total_time = 0;
10280 m_chart_drag_total_x = 0;
10281 m_chart_drag_total_y = 0;
10282 m_inertia_last_drag_x = x;
10283 m_inertia_last_drag_y = y;
10284 m_drag_vec_x.clear();
10285 m_drag_vec_y.clear();
10286 m_drag_vec_t.clear();
10287 m_last_drag_time = tnow;
10288 }
10289
10290 // Calculate and store drag dynamics.
10291 uint64_t delta_t = tnow - m_last_drag_time;
10292 double delta_tf = delta_t / 1e9;
10293
10294 m_chart_drag_total_time += delta_tf;
10295 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10296 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10297
10298 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10299 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10300 m_drag_vec_t.push_back(delta_tf);
10301
10302 m_inertia_last_drag_x = x;
10303 m_inertia_last_drag_y = y;
10304 m_last_drag_time = tnow;
10305
10306 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10307 m_bChartDragging = true;
10308 StartTimedMovement();
10309 m_pan_drag.x += last_drag.x - x;
10310 m_pan_drag.y += last_drag.y - y;
10311 last_drag.x = x, last_drag.y = y;
10312 }
10313 } else if (!g_btouch) {
10314 if ((last_drag.x != x) || (last_drag.y != y)) {
10315 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10316 // dragging on route create.
10317 // github #2994
10318 m_bChartDragging = true;
10319 StartTimedMovement();
10320 m_pan_drag.x += last_drag.x - x;
10321 m_pan_drag.y += last_drag.y - y;
10322 last_drag.x = x, last_drag.y = y;
10323 }
10324 }
10325 }
10326
10327 // Handle some special cases
10328 if (g_btouch) {
10329 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10330 // deactivate next LeftUp to ovoid creating an unexpected point
10331 m_ignore_next_leftup = true;
10332 m_DoubleClickTimer->Start();
10333 singleClickEventIsValid = false;
10334 }
10335 }
10336 }
10337
10338 return true;
10339}
10340
10341void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10342 if (MouseEventOverlayWindows(event)) return;
10343
10344 if (MouseEventSetup(event)) return; // handled, no further action required
10345
10346 bool nm = MouseEventProcessObjects(event);
10347 if (!nm) MouseEventProcessCanvas(event);
10348}
10349
10350void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10351 // Switch to the appropriate cursor on mouse movement
10352
10353 wxCursor *ptarget_cursor = pCursorArrow;
10354 if (!pPlugIn_Cursor) {
10355 ptarget_cursor = pCursorArrow;
10356 if ((!m_routeState) &&
10357 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10358 if (cursor_region == MID_RIGHT) {
10359 ptarget_cursor = pCursorRight;
10360 } else if (cursor_region == MID_LEFT) {
10361 ptarget_cursor = pCursorLeft;
10362 } else if (cursor_region == MID_TOP) {
10363 ptarget_cursor = pCursorDown;
10364 } else if (cursor_region == MID_BOT) {
10365 ptarget_cursor = pCursorUp;
10366 } else {
10367 ptarget_cursor = pCursorArrow;
10368 }
10369 } else if (m_bMeasure_Active ||
10370 m_routeState) // If Measure tool use Pencil Cursor
10371 ptarget_cursor = pCursorPencil;
10372 } else {
10373 ptarget_cursor = pPlugIn_Cursor;
10374 }
10375
10376 SetCursor(*ptarget_cursor);
10377}
10378
10379void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10380 SetCursor(*pCursorArrow);
10381}
10382
10383void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10384 ChartPlugInWrapper *target_plugin_chart = NULL;
10385 s57chart *Chs57 = NULL;
10386 wxFileName file;
10387 wxArrayString files;
10388
10389 ChartBase *target_chart = GetChartAtCursor();
10390 if (target_chart) {
10391 file.Assign(target_chart->GetFullPath());
10392 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10393 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10394 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10395 else
10396 Chs57 = dynamic_cast<s57chart *>(target_chart);
10397 } else { // target_chart = null, might be mbtiles
10398 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10399 unsigned int im = stackIndexArray.size();
10400 int scale = 2147483647; // max 32b integer
10401 if (VPoint.b_quilt && im > 0) {
10402 for (unsigned int is = 0; is < im; is++) {
10403 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10404 CHART_TYPE_MBTILES) {
10405 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10406 double lat, lon;
10407 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10408 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10409 .GetBBox()
10410 .Contains(lat, lon)) {
10411 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10412 scale) {
10413 scale =
10414 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10415 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10416 }
10417 }
10418 }
10419 }
10420 }
10421 }
10422
10423 std::vector<Ais8_001_22 *> area_notices;
10424
10425 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10426 float vp_scale = GetVPScale();
10427
10428 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10429 auto target_data = target.second;
10430 if (!target_data->area_notices.empty()) {
10431 for (auto &ani : target_data->area_notices) {
10432 Ais8_001_22 &area_notice = ani.second;
10433
10434 BoundingBox bbox;
10435
10436 for (Ais8_001_22_SubAreaList::iterator sa =
10437 area_notice.sub_areas.begin();
10438 sa != area_notice.sub_areas.end(); ++sa) {
10439 switch (sa->shape) {
10440 case AIS8_001_22_SHAPE_CIRCLE: {
10441 wxPoint target_point;
10442 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10443 bbox.Expand(target_point);
10444 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10445 break;
10446 }
10447 case AIS8_001_22_SHAPE_RECT: {
10448 wxPoint target_point;
10449 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10450 bbox.Expand(target_point);
10451 if (sa->e_dim_m > sa->n_dim_m)
10452 bbox.EnLarge(sa->e_dim_m * vp_scale);
10453 else
10454 bbox.EnLarge(sa->n_dim_m * vp_scale);
10455 break;
10456 }
10457 case AIS8_001_22_SHAPE_POLYGON:
10458 case AIS8_001_22_SHAPE_POLYLINE: {
10459 for (int i = 0; i < 4; ++i) {
10460 double lat = sa->latitude;
10461 double lon = sa->longitude;
10462 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10463 &lat, &lon);
10464 wxPoint target_point;
10465 GetCanvasPointPix(lat, lon, &target_point);
10466 bbox.Expand(target_point);
10467 }
10468 break;
10469 }
10470 case AIS8_001_22_SHAPE_SECTOR: {
10471 double lat1 = sa->latitude;
10472 double lon1 = sa->longitude;
10473 double lat, lon;
10474 wxPoint target_point;
10475 GetCanvasPointPix(lat1, lon1, &target_point);
10476 bbox.Expand(target_point);
10477 for (int i = 0; i < 18; ++i) {
10478 ll_gc_ll(
10479 lat1, lon1,
10480 sa->left_bound_deg +
10481 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10482 sa->radius_m / 1852.0, &lat, &lon);
10483 GetCanvasPointPix(lat, lon, &target_point);
10484 bbox.Expand(target_point);
10485 }
10486 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10487 &lat, &lon);
10488 GetCanvasPointPix(lat, lon, &target_point);
10489 bbox.Expand(target_point);
10490 break;
10491 }
10492 }
10493 }
10494
10495 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10496 area_notices.push_back(&area_notice);
10497 }
10498 }
10499 }
10500 }
10501 }
10502
10503 if (target_chart || !area_notices.empty() || file.HasName()) {
10504 // Go get the array of all objects at the cursor lat/lon
10505 int sel_rad_pix = 5;
10506 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10507
10508 // Make sure we always get the lights from an object, even if we are
10509 // currently not displaying lights on the chart.
10510
10511 SetCursor(wxCURSOR_WAIT);
10512 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10513 if (!lightsVis) SetShowENCLights(true);
10514 ;
10515
10516 ListOfObjRazRules *rule_list = NULL;
10517 ListOfPI_S57Obj *pi_rule_list = NULL;
10518 if (Chs57)
10519 rule_list =
10520 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10521 else if (target_plugin_chart)
10522 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10523 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10524
10525 ListOfObjRazRules *overlay_rule_list = NULL;
10526 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10527 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10528
10529 if (CHs57_Overlay) {
10530 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10531 zlat, zlon, SelectRadius, &GetVP());
10532 }
10533
10534 if (!lightsVis) SetShowENCLights(false);
10535
10536 wxString objText;
10537 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10538 wxString face = dFont->GetFaceName();
10539
10540 if (NULL == g_pObjectQueryDialog) {
10542 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10543 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10544 }
10545
10546 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10547 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10548
10549#ifdef __WXOSX__
10550 // Auto Adjustment for dark mode
10551 fg = g_pObjectQueryDialog->GetForegroundColour();
10552#endif
10553
10554 objText.Printf(
10555 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10556 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10557
10558#ifdef __WXOSX__
10559 int points = dFont->GetPointSize();
10560#else
10561 int points = dFont->GetPointSize() + 1;
10562#endif
10563
10564 int sizes[7];
10565 for (int i = -2; i < 5; i++) {
10566 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10567 }
10568 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10569
10570 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10571
10572 if (overlay_rule_list && CHs57_Overlay) {
10573 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10574 objText << "<hr noshade>";
10575 }
10576
10577 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10578 an != area_notices.end(); ++an) {
10579 objText << "<b>AIS Area Notice:</b> ";
10580 objText << ais8_001_22_notice_names[(*an)->notice_type];
10581 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10582 (*an)->sub_areas.begin();
10583 sa != (*an)->sub_areas.end(); ++sa)
10584 if (!sa->text.empty()) objText << sa->text;
10585 objText << "<br>expires: " << (*an)->expiry_time.Format();
10586 objText << "<hr noshade>";
10587 }
10588
10589 if (Chs57)
10590 objText << Chs57->CreateObjDescriptions(rule_list);
10591 else if (target_plugin_chart)
10592 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10593 pi_rule_list);
10594
10595 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10596
10597 // Add the additional info files
10598 wxString AddFiles, filenameOK;
10599 int filecount = 0;
10600 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10601 // plugin
10602
10603 AddFiles = wxString::Format(
10604 "<hr noshade><br><b>Additional info files attached to: </b> "
10605 "<font "
10606 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10607 "cellpadding=3>",
10608 file.GetFullName());
10609 file.Normalize();
10610 file.Assign(file.GetPath(), "");
10611 wxDir dir(file.GetFullPath());
10612 wxString filename;
10613 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10614 while (cont) {
10615 file.Assign(dir.GetNameWithSep().append(filename));
10616 wxString FormatString =
10617 "<td valign=top><font size=-2><a "
10618 "href=\"%s\">%s</a></font></td>";
10619 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10620 filenameOK = file.GetFullPath(); // remember last valid name
10621 // we are making a 3 columns table. New row only every third file
10622 if (3 * ((int)filecount / 3) == filecount)
10623 FormatString.Prepend("<tr>"); // new row
10624 else
10625 FormatString.Prepend(
10626 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10627 // spacer column
10628
10629 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10630 file.GetFullName());
10631 filecount++;
10632 }
10633 cont = dir.GetNext(&filename);
10634 }
10635 objText << AddFiles << "</table>";
10636 }
10637 objText << "</font>";
10638 objText << "</body></html>";
10639
10640 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10641 g_pObjectQueryDialog->SetHTMLPage(objText);
10642 g_pObjectQueryDialog->Show();
10643 }
10644 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10645 // generate an event to avoid double code
10646 wxHtmlLinkInfo hli(filenameOK);
10647 wxHtmlLinkEvent hle(1, hli);
10648 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10649 }
10650
10651 if (rule_list) rule_list->Clear();
10652 delete rule_list;
10653
10654 if (overlay_rule_list) overlay_rule_list->Clear();
10655 delete overlay_rule_list;
10656
10657 if (pi_rule_list) pi_rule_list->Clear();
10658 delete pi_rule_list;
10659
10660 SetCursor(wxCURSOR_ARROW);
10661 }
10662}
10663
10664void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10665 bool bNew = false;
10666 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10667 // Dialog
10668 g_pMarkInfoDialog = new MarkInfoDlg(this);
10669 bNew = true;
10670 }
10671
10672 if (1 /*g_bresponsive*/) {
10673 wxSize canvas_size = GetSize();
10674
10675 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10676 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10677
10678 g_pMarkInfoDialog->Layout();
10679
10680 wxPoint canvas_pos = GetPosition();
10681 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10682
10683 bool newFit = false;
10684 if (canvas_size.x < fitted_size.x) {
10685 fitted_size.x = canvas_size.x - 40;
10686 if (canvas_size.y < fitted_size.y)
10687 fitted_size.y -= 40; // scrollbar added
10688 }
10689 if (canvas_size.y < fitted_size.y) {
10690 fitted_size.y = canvas_size.y - 40;
10691 if (canvas_size.x < fitted_size.x)
10692 fitted_size.x -= 40; // scrollbar added
10693 }
10694
10695 if (newFit) {
10696 g_pMarkInfoDialog->SetSize(fitted_size);
10697 g_pMarkInfoDialog->Centre();
10698 }
10699 }
10700
10701 markPoint->m_bRPIsBeingEdited = false;
10702
10703 wxString title_base = _("Mark Properties");
10704 if (markPoint->m_bIsInRoute) {
10705 title_base = _("Waypoint Properties");
10706 }
10707 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10708 g_pMarkInfoDialog->UpdateProperties();
10709 if (markPoint->m_bIsInLayer) {
10710 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10711 GetLayerName(markPoint->m_LayerID)));
10712 g_pMarkInfoDialog->SetDialogTitle(caption);
10713 } else
10714 g_pMarkInfoDialog->SetDialogTitle(title_base);
10715
10716 g_pMarkInfoDialog->Show();
10717 g_pMarkInfoDialog->Raise();
10718 g_pMarkInfoDialog->InitialFocus();
10719 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10720}
10721
10722void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10723 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10724 pRoutePropDialog->SetRouteAndUpdate(selected);
10725 // pNew->UpdateProperties();
10726 pRoutePropDialog->Show();
10727 pRoutePropDialog->Raise();
10728 return;
10729 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10730 this); // There is one global instance of the RouteProp Dialog
10731
10732 if (g_bresponsive) {
10733 wxSize canvas_size = GetSize();
10734 wxPoint canvas_pos = GetPosition();
10735 wxSize fitted_size = pRoutePropDialog->GetSize();
10736 ;
10737
10738 if (canvas_size.x < fitted_size.x) {
10739 fitted_size.x = canvas_size.x;
10740 if (canvas_size.y < fitted_size.y)
10741 fitted_size.y -= 20; // scrollbar added
10742 }
10743 if (canvas_size.y < fitted_size.y) {
10744 fitted_size.y = canvas_size.y;
10745 if (canvas_size.x < fitted_size.x)
10746 fitted_size.x -= 20; // scrollbar added
10747 }
10748
10749 pRoutePropDialog->SetSize(fitted_size);
10750 pRoutePropDialog->Centre();
10751
10752 // int xp = (canvas_size.x - fitted_size.x)/2;
10753 // int yp = (canvas_size.y - fitted_size.y)/2;
10754
10755 wxPoint xxp = ClientToScreen(canvas_pos);
10756 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10757 }
10758
10759 pRoutePropDialog->SetRouteAndUpdate(selected);
10760
10761 pRoutePropDialog->Show();
10762
10763 Refresh(false);
10764}
10765
10766void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10767 pTrackPropDialog = TrackPropDlg::getInstance(
10768 this); // There is one global instance of the RouteProp Dialog
10769
10770 pTrackPropDialog->SetTrackAndUpdate(selected);
10772
10773 pTrackPropDialog->Show();
10774
10775 Refresh(false);
10776}
10777
10778void pupHandler_PasteWaypoint() {
10779 Kml kml;
10780
10781 int pasteBuffer = kml.ParsePasteBuffer();
10782 RoutePoint *pasted = kml.GetParsedRoutePoint();
10783 if (!pasted) return;
10784
10785 double nearby_radius_meters =
10786 g_Platform->GetSelectRadiusPix() /
10787 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10788
10789 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10790 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10791
10792 int answer = wxID_NO;
10793 if (nearPoint && !nearPoint->m_bIsInLayer) {
10794 wxString msg;
10795 msg << _(
10796 "There is an existing waypoint at the same location as the one you are "
10797 "pasting. Would you like to merge the pasted data with it?\n\n");
10798 msg << _("Answering 'No' will create a new waypoint at the same location.");
10799 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10800 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10801 }
10802
10803 if (answer == wxID_YES) {
10804 nearPoint->SetName(pasted->GetName());
10805 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10806 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10807 pRouteManagerDialog->UpdateWptListCtrl();
10808 }
10809
10810 if (answer == wxID_NO) {
10811 RoutePoint *newPoint = new RoutePoint(pasted);
10812 newPoint->m_bIsolatedMark = true;
10813 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10814 newPoint);
10815 // pConfig->AddNewWayPoint(newPoint, -1);
10816 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10817
10818 pWayPointMan->AddRoutePoint(newPoint);
10819 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10820 pRouteManagerDialog->UpdateWptListCtrl();
10821 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10822 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10823 }
10824
10825 gFrame->InvalidateAllGL();
10826 gFrame->RefreshAllCanvas(false);
10827}
10828
10829void pupHandler_PasteRoute() {
10830 Kml kml;
10831
10832 int pasteBuffer = kml.ParsePasteBuffer();
10833 Route *pasted = kml.GetParsedRoute();
10834 if (!pasted) return;
10835
10836 double nearby_radius_meters =
10837 g_Platform->GetSelectRadiusPix() /
10838 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10839
10840 RoutePoint *curPoint;
10841 RoutePoint *nearPoint;
10842 RoutePoint *prevPoint = NULL;
10843
10844 bool mergepoints = false;
10845 bool createNewRoute = true;
10846 int existingWaypointCounter = 0;
10847
10848 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10849 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10850 nearPoint = pWayPointMan->GetNearbyWaypoint(
10851 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10852 if (nearPoint) {
10853 mergepoints = true;
10854 existingWaypointCounter++;
10855 // Small hack here to avoid both extending RoutePoint and repeating all
10856 // the GetNearbyWaypoint calculations. Use existin data field in
10857 // RoutePoint as temporary storage.
10858 curPoint->m_bPtIsSelected = true;
10859 }
10860 }
10861
10862 int answer = wxID_NO;
10863 if (mergepoints) {
10864 wxString msg;
10865 msg << _(
10866 "There are existing waypoints at the same location as some of the ones "
10867 "you are pasting. Would you like to just merge the pasted data into "
10868 "them?\n\n");
10869 msg << _("Answering 'No' will create all new waypoints for this route.");
10870 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10871 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10872
10873 if (answer == wxID_CANCEL) {
10874 return;
10875 }
10876 }
10877
10878 // If all waypoints exist since before, and a route with the same name, we
10879 // don't create a new route.
10880 if (mergepoints && answer == wxID_YES &&
10881 existingWaypointCounter == pasted->GetnPoints()) {
10882 for (Route *proute : *pRouteList) {
10883 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10884 createNewRoute = false;
10885 break;
10886 }
10887 }
10888 }
10889
10890 Route *newRoute = 0;
10891 RoutePoint *newPoint = 0;
10892
10893 if (createNewRoute) {
10894 newRoute = new Route();
10895 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10896 }
10897
10898 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10899 curPoint = pasted->GetPoint(i);
10900 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10901 curPoint->m_bPtIsSelected = false;
10902 newPoint = pWayPointMan->GetNearbyWaypoint(
10903 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10904 newPoint->SetName(curPoint->GetName());
10905 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10906
10907 if (createNewRoute) newRoute->AddPoint(newPoint);
10908 } else {
10909 curPoint->m_bPtIsSelected = false;
10910
10911 newPoint = new RoutePoint(curPoint);
10912 newPoint->m_bIsolatedMark = false;
10913 newPoint->SetIconName("circle");
10914 newPoint->m_bIsVisible = true;
10915 newPoint->m_bShowName = false;
10916 newPoint->SetShared(false);
10917
10918 newRoute->AddPoint(newPoint);
10919 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10920 newPoint);
10921 // pConfig->AddNewWayPoint(newPoint, -1);
10922 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10923 pWayPointMan->AddRoutePoint(newPoint);
10924 }
10925 if (i > 1 && createNewRoute)
10926 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10927 curPoint->m_lat, curPoint->m_lon,
10928 prevPoint, newPoint, newRoute);
10929 prevPoint = newPoint;
10930 }
10931
10932 if (createNewRoute) {
10933 pRouteList->push_back(newRoute);
10934 // pConfig->AddNewRoute(newRoute); // use auto next num
10935 NavObj_dB::GetInstance().InsertRoute(newRoute);
10936
10937 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10938 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10939 }
10940
10941 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10942 pRouteManagerDialog->UpdateRouteListCtrl();
10943 pRouteManagerDialog->UpdateWptListCtrl();
10944 }
10945 gFrame->InvalidateAllGL();
10946 gFrame->RefreshAllCanvas(false);
10947 }
10948 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10949 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10950}
10951
10952void pupHandler_PasteTrack() {
10953 Kml kml;
10954
10955 int pasteBuffer = kml.ParsePasteBuffer();
10956 Track *pasted = kml.GetParsedTrack();
10957 if (!pasted) return;
10958
10959 TrackPoint *curPoint;
10960
10961 Track *newTrack = new Track();
10962 TrackPoint *newPoint;
10963 TrackPoint *prevPoint = NULL;
10964
10965 newTrack->SetName(pasted->GetName());
10966
10967 for (int i = 0; i < pasted->GetnPoints(); i++) {
10968 curPoint = pasted->GetPoint(i);
10969
10970 newPoint = new TrackPoint(curPoint);
10971
10972 wxDateTime now = wxDateTime::Now();
10973 newPoint->SetCreateTime(curPoint->GetCreateTime());
10974
10975 newTrack->AddPoint(newPoint);
10976
10977 if (prevPoint)
10978 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10979 newPoint->m_lat, newPoint->m_lon,
10980 prevPoint, newPoint, newTrack);
10981
10982 prevPoint = newPoint;
10983 }
10984
10985 g_TrackList.push_back(newTrack);
10986 // pConfig->AddNewTrack(newTrack);
10987 NavObj_dB::GetInstance().InsertTrack(newTrack);
10988
10989 gFrame->InvalidateAllGL();
10990 gFrame->RefreshAllCanvas(false);
10991}
10992
10993bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
10994 wxJSONValue v;
10995 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
10996 v["CursorPosition_x"] = x;
10997 v["CursorPosition_y"] = y;
10998 // Send a limited set of selection types depending on what is
10999 // found under the mouse point.
11000 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11001 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11002 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11003
11004 wxJSONWriter w;
11005 wxString out;
11006 w.Write(v, out);
11007 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11008
11009 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11010
11011#if 0
11012#define SELTYPE_UNKNOWN 0x0001
11013#define SELTYPE_ROUTEPOINT 0x0002
11014#define SELTYPE_ROUTESEGMENT 0x0004
11015#define SELTYPE_TIDEPOINT 0x0008
11016#define SELTYPE_CURRENTPOINT 0x0010
11017#define SELTYPE_ROUTECREATE 0x0020
11018#define SELTYPE_AISTARGET 0x0040
11019#define SELTYPE_MARKPOINT 0x0080
11020#define SELTYPE_TRACKSEGMENT 0x0100
11021#define SELTYPE_DRAGHANDLE 0x0200
11022#endif
11023
11024 if (g_bhide_context_menus) return true;
11025 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11026 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11027 m_pIDXCandidate, m_nmea_log);
11028
11029 Connect(
11030 wxEVT_COMMAND_MENU_SELECTED,
11031 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11032
11033#ifdef __WXGTK__
11034 // Funny requirement here for gtk, to clear the menu trigger event
11035 // TODO
11036 // Causes a slight "flasH" of the menu,
11037 if (m_inLongPress) {
11038 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11039 m_inLongPress = false;
11040 }
11041#endif
11042
11043 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11044
11045 Disconnect(
11046 wxEVT_COMMAND_MENU_SELECTED,
11047 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11048
11049 delete m_canvasMenu;
11050 m_canvasMenu = NULL;
11051
11052#ifdef __WXQT__
11053 // gFrame->SurfaceToolbar();
11054 // g_MainToolbar->Raise();
11055#endif
11056
11057 return true;
11058}
11059
11060void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11061 // Pass menu events from the canvas to the menu handler
11062 // This is necessarily in ChartCanvas since that is the menu's parent.
11063 if (m_canvasMenu) {
11064 m_canvasMenu->PopupMenuHandler(event);
11065 }
11066 return;
11067}
11068
11069void ChartCanvas::StartRoute() {
11070 // Do not allow more than one canvas to create a route at one time.
11071 if (g_brouteCreating) return;
11072
11073 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11074
11075 g_brouteCreating = true;
11076 m_routeState = 1;
11077 m_bDrawingRoute = false;
11078 SetCursor(*pCursorPencil);
11079 // SetCanvasToolbarItemState(ID_ROUTE, true);
11080 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11081
11082 HideGlobalToolbar();
11083
11084#ifdef __ANDROID__
11085 androidSetRouteAnnunciator(true);
11086#endif
11087}
11088
11089wxString ChartCanvas::FinishRoute() {
11090 m_routeState = 0;
11091 m_prev_pMousePoint = NULL;
11092 m_bDrawingRoute = false;
11093 wxString rv = "";
11094 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11095
11096 // SetCanvasToolbarItemState(ID_ROUTE, false);
11097 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11098#ifdef __ANDROID__
11099 androidSetRouteAnnunciator(false);
11100#endif
11101
11102 SetCursor(*pCursorArrow);
11103
11104 if (m_pMouseRoute) {
11105 if (m_bAppendingRoute) {
11106 // pConfig->UpdateRoute(m_pMouseRoute);
11107 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11108 } else {
11109 if (m_pMouseRoute->GetnPoints() > 1) {
11110 // pConfig->AddNewRoute(m_pMouseRoute);
11111 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11112 } else {
11113 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11114 m_pMouseRoute = NULL;
11115 }
11116 }
11117 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11118
11119 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11120 (pRoutePropDialog->IsShown())) {
11121 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11122 }
11123
11124 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11125 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11126 pRouteManagerDialog->UpdateRouteListCtrl();
11127 }
11128 }
11129 m_bAppendingRoute = false;
11130 m_pMouseRoute = NULL;
11131
11132 m_pSelectedRoute = NULL;
11133
11134 undo->InvalidateUndo();
11135 gFrame->RefreshAllCanvas(true);
11136
11137 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11138
11139 ShowGlobalToolbar();
11140
11141 g_brouteCreating = false;
11142
11143 return rv;
11144}
11145
11146void ChartCanvas::HideGlobalToolbar() {
11147 if (m_canvasIndex == 0) {
11148 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
11149 }
11150}
11151
11152void ChartCanvas::ShowGlobalToolbar() {
11153 if (m_canvasIndex == 0) {
11154 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
11155 }
11156}
11157
11158void ChartCanvas::ShowAISTargetList() {
11159 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11160 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11161 }
11162
11163 g_pAISTargetList->UpdateAISTargetList();
11164}
11165
11166void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11167 if (!m_bShowOutlines) return;
11168
11169 if (!ChartData) return;
11170
11171 int nEntry = ChartData->GetChartTableEntries();
11172
11173 for (int i = 0; i < nEntry; i++) {
11174 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11175
11176 // Check to see if the candidate chart is in the currently active group
11177 bool b_group_draw = false;
11178 if (m_groupIndex > 0) {
11179 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11180 int index = pt->GetGroupArray()[ig];
11181 if (m_groupIndex == index) {
11182 b_group_draw = true;
11183 break;
11184 }
11185 }
11186 } else
11187 b_group_draw = true;
11188
11189 if (b_group_draw) RenderChartOutline(dc, i, vp);
11190 }
11191
11192 // On CM93 Composite Charts, draw the outlines of the next smaller
11193 // scale cell
11194 cm93compchart *pcm93 = NULL;
11195 if (VPoint.b_quilt) {
11196 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11197 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11198 pcm93 = (cm93compchart *)pch;
11199 break;
11200 }
11201 } else if (m_singleChart &&
11202 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11203 pcm93 = (cm93compchart *)m_singleChart;
11204
11205 if (pcm93) {
11206 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11207 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11208
11209 if (zoom_factor > 8.0) {
11210 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11211 dc.SetPen(mPen);
11212 } else {
11213 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11214 dc.SetPen(mPen);
11215 }
11216
11217 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11218 }
11219}
11220
11221void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11222#ifdef ocpnUSE_GL
11223 if (g_bopengl && m_glcc) {
11224 /* opengl version specially optimized */
11225 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11226 return;
11227 }
11228#endif
11229
11230 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11231 if (!ChartData->IsChartAvailable(dbIndex)) return;
11232 }
11233
11234 float plylat, plylon;
11235 float plylat1, plylon1;
11236
11237 int pixx, pixy, pixx1, pixy1;
11238
11239 LLBBox box;
11240 ChartData->GetDBBoundingBox(dbIndex, box);
11241
11242 // Don't draw an outline in the case where the chart covers the entire world
11243 // */
11244 if (box.GetLonRange() == 360) return;
11245
11246 double lon_bias = 0;
11247 // chart is outside of viewport lat/lon bounding box
11248 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11249
11250 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11251
11252 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11253 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11254
11255 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11256 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11257
11258 else
11259 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11260
11261 // Are there any aux ply entries?
11262 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11263 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11264 {
11265 wxPoint r, r1;
11266
11267 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11268 plylon += lon_bias;
11269
11270 GetCanvasPointPix(plylat, plylon, &r);
11271 pixx = r.x;
11272 pixy = r.y;
11273
11274 for (int i = 0; i < nPly - 1; i++) {
11275 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11276 plylon1 += lon_bias;
11277
11278 GetCanvasPointPix(plylat1, plylon1, &r1);
11279 pixx1 = r1.x;
11280 pixy1 = r1.y;
11281
11282 int pixxs1 = pixx1;
11283 int pixys1 = pixy1;
11284
11285 bool b_skip = false;
11286
11287 if (vp.chart_scale > 5e7) {
11288 // calculate projected distance between these two points in meters
11289 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11290 pow((double)(pixy1 - pixy), 2)) /
11291 vp.view_scale_ppm;
11292
11293 if (dist > 0.0) {
11294 // calculate GC distance between these two points in meters
11295 double distgc =
11296 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11297
11298 // If the distances are nonsense, it means that the scale is very
11299 // small and the segment wrapped the world So skip it....
11300 // TODO improve this to draw two segments
11301 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11302 b_skip = true;
11303 } else
11304 b_skip = true;
11305 }
11306
11307 ClipResult res = cohen_sutherland_line_clip_i(
11308 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11309 if (res != Invisible && !b_skip)
11310 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11311
11312 plylat = plylat1;
11313 plylon = plylon1;
11314 pixx = pixxs1;
11315 pixy = pixys1;
11316 }
11317
11318 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11319 plylon1 += lon_bias;
11320
11321 GetCanvasPointPix(plylat1, plylon1, &r1);
11322 pixx1 = r1.x;
11323 pixy1 = r1.y;
11324
11325 ClipResult res = cohen_sutherland_line_clip_i(
11326 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11327 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11328 }
11329
11330 else // Use Aux PlyPoints
11331 {
11332 wxPoint r, r1;
11333
11334 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11335 for (int j = 0; j < nAuxPlyEntries; j++) {
11336 int nAuxPly =
11337 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11338 GetCanvasPointPix(plylat, plylon, &r);
11339 pixx = r.x;
11340 pixy = r.y;
11341
11342 for (int i = 0; i < nAuxPly - 1; i++) {
11343 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11344
11345 GetCanvasPointPix(plylat1, plylon1, &r1);
11346 pixx1 = r1.x;
11347 pixy1 = r1.y;
11348
11349 int pixxs1 = pixx1;
11350 int pixys1 = pixy1;
11351
11352 bool b_skip = false;
11353
11354 if (vp.chart_scale > 5e7) {
11355 // calculate projected distance between these two points in meters
11356 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11357 ((pixy1 - pixy) * (pixy1 - pixy))) /
11358 vp.view_scale_ppm;
11359 if (dist > 0.0) {
11360 // calculate GC distance between these two points in meters
11361 double distgc =
11362 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11363
11364 // If the distances are nonsense, it means that the scale is very
11365 // small and the segment wrapped the world So skip it....
11366 // TODO improve this to draw two segments
11367 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11368 b_skip = true;
11369 } else
11370 b_skip = true;
11371 }
11372
11373 ClipResult res = cohen_sutherland_line_clip_i(
11374 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11375 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11376
11377 plylat = plylat1;
11378 plylon = plylon1;
11379 pixx = pixxs1;
11380 pixy = pixys1;
11381 }
11382
11383 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11384 GetCanvasPointPix(plylat1, plylon1, &r1);
11385 pixx1 = r1.x;
11386 pixy1 = r1.y;
11387
11388 ClipResult res = cohen_sutherland_line_clip_i(
11389 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11390 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11391 }
11392 }
11393}
11394
11395static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
11396 const wxString &second) {
11397 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11398
11399 int pointsize = dFont->GetPointSize();
11400 pointsize /= OCPN_GetWinDIPScaleFactor();
11401
11402 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11403 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11404 false, dFont->GetFaceName());
11405
11406 dc.SetFont(*psRLI_font);
11407
11408 int w1, h1;
11409 int w2 = 0;
11410 int h2 = 0;
11411 int h, w;
11412
11413 int xp, yp;
11414 int hilite_offset = 3;
11415#ifdef __WXMAC__
11416 wxScreenDC sdc;
11417 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
11418 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
11419#else
11420 dc.GetTextExtent(first, &w1, &h1);
11421 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
11422#endif
11423
11424 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11425 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11426
11427 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
11428 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11429
11430 h = h1 + h2;
11431
11432 xp = ref_point.x - w;
11433 yp = ref_point.y;
11434 yp += hilite_offset;
11435
11436 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11437
11438 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11439 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11440
11441 dc.DrawText(first, xp, yp);
11442 if (second.Len()) dc.DrawText(second, xp, yp + h1);
11443}
11444
11445void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11446 if (!g_bAllowShipToActive) return;
11447
11448 Route *rt = g_pRouteMan->GetpActiveRoute();
11449 if (!rt) return;
11450
11451 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11452 wxPoint2DDouble pa, pb;
11454 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11455
11456 // set pen
11457 int width =
11458 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11459 if (rt->m_width != wxPENSTYLE_INVALID)
11460 width = rt->m_width; // set route pen style if any
11461 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11462 g_shipToActiveStyle, 5)]; // get setting pen style
11463 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11464 wxColour color =
11465 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11466 : // set setting route pen color
11467 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11468 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11469
11470 dc.SetPen(*mypen);
11471 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11472
11473 if (!Use_Opengl)
11474 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11475 (int)pb.m_y, GetVP(), true);
11476
11477#ifdef ocpnUSE_GL
11478 else {
11479#ifdef USE_ANDROID_GLES2
11480 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11481#else
11482 if (style != wxPENSTYLE_SOLID) {
11483 if (glChartCanvas::dash_map.find(style) !=
11484 glChartCanvas::dash_map.end()) {
11485 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11486 dc.SetPen(*mypen);
11487 }
11488 }
11489 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11490#endif
11491
11492 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11493 (int)pb.m_x, (int)pb.m_y, GetVP());
11494 }
11495#endif
11496 }
11497}
11498
11499void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11500 Route *route = 0;
11501 if (m_routeState >= 2) route = m_pMouseRoute;
11502 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11503 route = m_pMeasureRoute;
11504
11505 if (!route) return;
11506
11507 // Validate route pointer
11508 if (!g_pRouteMan->IsRouteValid(route)) return;
11509
11510 double render_lat = m_cursor_lat;
11511 double render_lon = m_cursor_lon;
11512
11513 int np = route->GetnPoints();
11514 if (np) {
11515 if (g_btouch && (np > 1)) np--;
11516 RoutePoint rp = route->GetPoint(np);
11517 render_lat = rp.m_lat;
11518 render_lon = rp.m_lon;
11519 }
11520
11521 double rhumbBearing, rhumbDist;
11522 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11523 &rhumbBearing, &rhumbDist);
11524 double brg = rhumbBearing;
11525 double dist = rhumbDist;
11526
11527 // Skip GreatCircle rubberbanding on touch devices.
11528 if (!g_btouch) {
11529 double gcBearing, gcBearing2, gcDist;
11530 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11531 m_cursor_lat, &gcDist, &gcBearing,
11532 &gcBearing2);
11533 double gcDistm = gcDist / 1852.0;
11534
11535 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11536 rhumbBearing = 90.;
11537
11538 wxPoint destPoint, lastPoint;
11539
11540 route->m_NextLegGreatCircle = false;
11541 int milesDiff = rhumbDist - gcDistm;
11542 if (milesDiff > 1) {
11543 brg = gcBearing;
11544 dist = gcDistm;
11545 route->m_NextLegGreatCircle = true;
11546 }
11547
11548 // FIXME (MacOS, the first segment is rendered wrong)
11549 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11550 &lastPoint);
11551
11552 if (route->m_NextLegGreatCircle) {
11553 for (int i = 1; i <= milesDiff; i++) {
11554 double p = (double)i * (1.0 / (double)milesDiff);
11555 double pLat, pLon;
11556 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11557 &pLon, &pLat, &gcBearing2);
11558 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11559 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11560 false);
11561 lastPoint = destPoint;
11562 }
11563 } else {
11564 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11565 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11566 false);
11567 if (m_bMeasure_DistCircle) {
11568 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11569 powf((float)(r_rband.y - lastPoint.y), 2));
11570
11571 dc.SetPen(*g_pRouteMan->GetRoutePen());
11572 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11573 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11574 }
11575 }
11576 }
11577 }
11578
11579 wxString routeInfo;
11580 double varBrg = 0;
11581 if (g_bShowTrue)
11582 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11583 0x00B0);
11584
11585 if (g_bShowMag) {
11586 double latAverage = (m_cursor_lat + render_lat) / 2;
11587 double lonAverage = (m_cursor_lon + render_lon) / 2;
11588 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11589
11590 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11591 (int)varBrg, 0x00B0);
11592 }
11593 routeInfo << " " << FormatDistanceAdaptive(dist);
11594
11595 // To make it easier to use a route as a bearing on a charted object add for
11596 // the first leg also the reverse bearing.
11597 if (np == 1) {
11598 routeInfo << "\nReverse: ";
11599 if (g_bShowTrue)
11600 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11601 (int)(brg + 180.) % 360, 0x00B0);
11602 if (g_bShowMag)
11603 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11604 (int)(varBrg + 180.) % 360, 0x00B0);
11605 }
11606
11607 wxString s0;
11608 if (!route->m_bIsInLayer)
11609 s0.Append(_("Route") + ": ");
11610 else
11611 s0.Append(_("Layer Route: "));
11612
11613 double disp_length = route->m_route_length;
11614 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11615 s0 += FormatDistanceAdaptive(disp_length);
11616
11617 RouteLegInfo(dc, r_rband, routeInfo, s0);
11618
11619 m_brepaint_piano = true;
11620}
11621
11622void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11623 if (!m_bShowVisibleSectors) return;
11624
11625 if (g_bDeferredInitDone) {
11626 // need to re-evaluate sectors?
11627 double rhumbBearing, rhumbDist;
11628 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11629 &rhumbBearing, &rhumbDist);
11630
11631 if (rhumbDist > 0.05) // miles
11632 {
11633 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11634 m_sectorlegsVisible);
11635 m_sector_glat = gLat;
11636 m_sector_glon = gLon;
11637 }
11638 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11639 }
11640}
11641
11642void ChartCanvas::WarpPointerDeferred(int x, int y) {
11643 warp_x = x;
11644 warp_y = y;
11645 warp_flag = true;
11646}
11647
11648int s_msg;
11649
11650void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11651 if (!ps52plib) return;
11652
11653 if (VPoint.b_quilt) { // quilted
11654 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11655
11656 if (m_pQuilt->IsQuiltVector()) {
11657 if (ps52plib->GetStateHash() != m_s52StateHash) {
11658 UpdateS52State();
11659 m_s52StateHash = ps52plib->GetStateHash();
11660 }
11661 }
11662 } else {
11663 if (ps52plib->GetStateHash() != m_s52StateHash) {
11664 UpdateS52State();
11665 m_s52StateHash = ps52plib->GetStateHash();
11666 }
11667 }
11668
11669 // Plugin charts
11670 bool bSendPlibState = true;
11671 if (VPoint.b_quilt) { // quilted
11672 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11673 }
11674
11675 if (bSendPlibState) {
11676 wxJSONValue v;
11677 v["OpenCPN Version Major"] = VERSION_MAJOR;
11678 v["OpenCPN Version Minor"] = VERSION_MINOR;
11679 v["OpenCPN Version Patch"] = VERSION_PATCH;
11680 v["OpenCPN Version Date"] = VERSION_DATE;
11681 v["OpenCPN Version Full"] = VERSION_FULL;
11682
11683 // S52PLIB state
11684 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11685 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11686 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11687 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11688 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11689 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11690 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11691
11692 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11693
11694 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11695 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11696
11697 // Global S52 options
11698
11699 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11700 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11701 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11702 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11703 ps52plib->m_bShowS57ImportantTextOnly;
11704 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11705 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11706 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11707 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11708 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11709
11710 // Some global GUI parameters, for completeness
11711 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11712 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11713 v["OpenCPN Scale Factor Exp"] =
11714 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11715 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11716
11717 wxJSONWriter w;
11718 wxString out;
11719 w.Write(v, out);
11720
11721 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11722 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11723 g_lastS52PLIBPluginMessage = out;
11724 }
11725 }
11726}
11727int spaint;
11728int s_in_update;
11729void ChartCanvas::OnPaint(wxPaintEvent &event) {
11730 wxPaintDC dc(this);
11731
11732 // GetToolbar()->Show( m_bToolbarEnable );
11733
11734 // Paint updates may have been externally disabled (temporarily, to avoid
11735 // Yield() recursion performance loss) It is important that the wxPaintDC is
11736 // built, even if we elect to not process this paint message. Otherwise, the
11737 // paint message may not be removed from the message queue, esp on Windows.
11738 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11739
11740 if (!m_b_paint_enable) {
11741 return;
11742 }
11743
11744 // If necessary, reconfigure the S52 PLIB
11746
11747#ifdef ocpnUSE_GL
11748 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11749
11750 if (m_glcc && g_bopengl) {
11751 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11752 s_in_update++;
11753 m_glcc->Update();
11754 s_in_update--;
11755 }
11756
11757 return;
11758 }
11759#endif
11760
11761 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11762
11763 wxRegion ru = GetUpdateRegion();
11764
11765 int rx, ry, rwidth, rheight;
11766 ru.GetBox(rx, ry, rwidth, rheight);
11767
11768#ifdef ocpnUSE_DIBSECTION
11769 ocpnMemDC temp_dc;
11770#else
11771 wxMemoryDC temp_dc;
11772#endif
11773
11774 long height = GetVP().pix_height;
11775
11776#ifdef __WXMAC__
11777 // On OS X we have to explicitly extend the region for the piano area
11778 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11779 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11780 height += m_Piano->GetHeight();
11781#endif // __WXMAC__
11782 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11783
11784 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11785 if (pthumbwin) {
11786 int thumbx, thumby, thumbsx, thumbsy;
11787 pthumbwin->GetPosition(&thumbx, &thumby);
11788 pthumbwin->GetSize(&thumbsx, &thumbsy);
11789 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11790
11791 if (pthumbwin->IsShown()) {
11792 rgn_chart.Subtract(rgn_thumbwin);
11793 ru.Subtract(rgn_thumbwin);
11794 }
11795 }
11796
11797 // subtract the chart bar if it isn't transparent, and determine if we need to
11798 // paint it
11799 wxRegion rgn_blit = ru;
11800 if (g_bShowChartBar) {
11801 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11802 GetClientSize().x, m_Piano->GetHeight());
11803
11804 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11805 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11806 if (style->chartStatusWindowTransparent)
11807 m_brepaint_piano = true;
11808 else
11809 ru.Subtract(chart_bar_rect);
11810 }
11811 }
11812
11813 if (m_Compass && m_Compass->IsShown()) {
11814 wxRect compassRect = m_Compass->GetRect();
11815 if (ru.Contains(compassRect) != wxOutRegion) {
11816 ru.Subtract(compassRect);
11817 }
11818 }
11819
11820 if (m_notification_button) {
11821 wxRect noteRect = m_notification_button->GetRect();
11822 if (ru.Contains(noteRect) != wxOutRegion) {
11823 ru.Subtract(noteRect);
11824 }
11825 }
11826
11827 // Is this viewpoint the same as the previously painted one?
11828 bool b_newview = true;
11829
11830 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11831 (m_cache_vp.rotation == VPoint.rotation) &&
11832 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11833 m_cache_vp.IsValid()) {
11834 b_newview = false;
11835 }
11836
11837 // If the ViewPort is skewed or rotated, we may be able to use the cached
11838 // rotated bitmap.
11839 bool b_rcache_ok = false;
11840 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11841 b_rcache_ok = !b_newview;
11842
11843 // Make a special VP
11844 if (VPoint.b_MercatorProjectionOverride)
11845 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11846 ViewPort svp = VPoint;
11847
11848 svp.pix_width = svp.rv_rect.width;
11849 svp.pix_height = svp.rv_rect.height;
11850
11851 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11852 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11853 // VPoint.rv_rect.height);
11854
11855 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11856
11857 // If we are going to use the cached rotated image, there is no need to fetch
11858 // any chart data and this will do it...
11859 if (b_rcache_ok) chart_get_region.Clear();
11860
11861 // Blit pan acceleration
11862 if (VPoint.b_quilt) // quilted
11863 {
11864 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11865
11866 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11867
11868 bool busy = false;
11869 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11870 m_cache_vp.rotation != VPoint.rotation)) {
11871 AbstractPlatform::ShowBusySpinner();
11872 busy = true;
11873 }
11874
11875 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11876 (m_working_bm.GetHeight() != svp.pix_height))
11877 m_working_bm.Create(svp.pix_width, svp.pix_height,
11878 -1); // make sure the target is big enoug
11879
11880 if (fabs(VPoint.rotation) < 0.01) {
11881 bool b_save = true;
11882
11883 if (g_SencThreadManager) {
11884 if (g_SencThreadManager->GetJobCount()) {
11885 b_save = false;
11886 m_cache_vp.Invalidate();
11887 }
11888 }
11889
11890 // If the saved wxBitmap from last OnPaint is useable
11891 // calculate the blit parameters
11892
11893 // We can only do screen blit painting if subsequent ViewPorts differ by
11894 // whole pixels So, in small scale bFollow mode, force the full screen
11895 // render. This seems a hack....There may be better logic here.....
11896
11897 // if(m_bFollow)
11898 // b_save = false;
11899
11900 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11901 if (b_newview) {
11902 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11903 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11904
11905 int dy = c_new.y - c_old.y;
11906 int dx = c_new.x - c_old.x;
11907
11908 // printf("In OnPaint Trying Blit dx: %d
11909 // dy:%d\n\n", dx, dy);
11910
11911 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11912 if (dx || dy) {
11913 // Blit the reuseable portion of the cached wxBitmap to a working
11914 // bitmap
11915 temp_dc.SelectObject(m_working_bm);
11916
11917 wxMemoryDC cache_dc;
11918 cache_dc.SelectObject(m_cached_chart_bm);
11919
11920 if (dy > 0) {
11921 if (dx > 0) {
11922 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11923 VPoint.pix_height - dy, &cache_dc, dx, dy);
11924 } else {
11925 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11926 VPoint.pix_height - dy, &cache_dc, 0, dy);
11927 }
11928
11929 } else {
11930 if (dx > 0) {
11931 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11932 VPoint.pix_height + dy, &cache_dc, dx, 0);
11933 } else {
11934 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11935 VPoint.pix_height + dy, &cache_dc, 0, 0);
11936 }
11937 }
11938
11939 OCPNRegion update_region;
11940 if (dy) {
11941 if (dy > 0)
11942 update_region.Union(
11943 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11944 else
11945 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11946 }
11947
11948 if (dx) {
11949 if (dx > 0)
11950 update_region.Union(
11951 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11952 else
11953 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11954 }
11955
11956 // Render the new region
11957 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11958 update_region);
11959 cache_dc.SelectObject(wxNullBitmap);
11960 } else {
11961 // No sensible (dx, dy) change in the view, so use the cached
11962 // member bitmap
11963 temp_dc.SelectObject(m_cached_chart_bm);
11964 b_save = false;
11965 }
11966 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11967
11968 } else // not blitable
11969 {
11970 temp_dc.SelectObject(m_working_bm);
11971 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11972 chart_get_region);
11973 }
11974 } else {
11975 // No change in the view, so use the cached member bitmap2
11976 temp_dc.SelectObject(m_cached_chart_bm);
11977 b_save = false;
11978 }
11979 } else // cached bitmap is not yet valid
11980 {
11981 temp_dc.SelectObject(m_working_bm);
11982 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11983 chart_get_region);
11984 }
11985
11986 // Save the fully rendered quilt image as a wxBitmap member of this class
11987 if (b_save) {
11988 // if((m_cached_chart_bm.GetWidth() !=
11989 // svp.pix_width) ||
11990 // (m_cached_chart_bm.GetHeight() !=
11991 // svp.pix_height))
11992 // m_cached_chart_bm.Create(svp.pix_width,
11993 // svp.pix_height, -1); // target wxBitmap
11994 // is big enough
11995 wxMemoryDC scratch_dc_0;
11996 scratch_dc_0.SelectObject(m_cached_chart_bm);
11997 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11998
11999 scratch_dc_0.SelectObject(wxNullBitmap);
12000
12001 m_bm_cache_vp =
12002 VPoint; // save the ViewPort associated with the cached wxBitmap
12003 }
12004 }
12005
12006 else // quilted, rotated
12007 {
12008 temp_dc.SelectObject(m_working_bm);
12009 OCPNRegion chart_get_all_region(
12010 wxRect(0, 0, svp.pix_width, svp.pix_height));
12011 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12012 chart_get_all_region);
12013 }
12014
12015 AbstractPlatform::HideBusySpinner();
12016
12017 }
12018
12019 else // not quilted
12020 {
12021 if (!m_singleChart) {
12022 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12023 dc.Clear();
12024 return;
12025 }
12026
12027 if (!chart_get_region.IsEmpty()) {
12028 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12029 }
12030 }
12031
12032 if (temp_dc.IsOk()) {
12033 // Arrange to render the World Chart vector data behind the rendered
12034 // current chart so that uncovered canvas areas show at least the world
12035 // chart.
12036 OCPNRegion chartValidRegion;
12037 if (!VPoint.b_quilt) {
12038 // Make a region covering the current chart on the canvas
12039
12040 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12041 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12042 else {
12043 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12044 // require that the viewport passed here have pix_width and pix_height
12045 // set to the actual display, not the virtual (rv_rect) sizes
12046 // (the vector calculations require the virtual sizes in svp)
12047
12048 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12049 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12050 }
12051 } else
12052 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12053
12054 temp_dc.DestroyClippingRegion();
12055
12056 // Copy current chart region
12057 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12058
12059 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12060
12061 if (!backgroundRegion.IsEmpty()) {
12062 // Draw the Background Chart only in the areas NOT covered by the
12063 // current chart view
12064
12065 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12066 clipping regions with more than 1 rectangle so... */
12067 wxColour water = pWorldBackgroundChart->water;
12068 if (water.IsOk()) {
12069 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12070 temp_dc.SetBrush(wxBrush(water));
12071 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12072 while (upd.HaveRects()) {
12073 wxRect rect = upd.GetRect();
12074 temp_dc.DrawRectangle(rect);
12075 upd.NextRect();
12076 }
12077 }
12078 // Associate with temp_dc
12079 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12080 temp_dc.SetDeviceClippingRegion(*clip_region);
12081 delete clip_region;
12082
12083 ocpnDC bgdc(temp_dc);
12084 double r = VPoint.rotation;
12085 SetVPRotation(VPoint.skew);
12086
12087 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12088 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12089
12090 SetVPRotation(r);
12091 }
12092 } // temp_dc.IsOk();
12093
12094 wxMemoryDC *pChartDC = &temp_dc;
12095 wxMemoryDC rotd_dc;
12096
12097 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12098 // Can we use the current rotated image cache?
12099 if (!b_rcache_ok) {
12100#ifdef __WXMSW__
12101 wxMemoryDC tbase_dc;
12102 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12103 tbase_dc.SelectObject(bm_base);
12104 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12105 tbase_dc.SelectObject(wxNullBitmap);
12106#else
12107 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12108#endif
12109
12110 wxImage base_image;
12111 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12112
12113 // Use a local static image rotator to improve wxWidgets code profile
12114 // Especially, on GTK the wxRound and wxRealPoint functions are very
12115 // expensive.....
12116
12117 double angle = GetVP().skew - GetVP().rotation;
12118 wxImage ri;
12119 bool b_rot_ok = false;
12120 if (base_image.IsOk()) {
12121 ViewPort rot_vp = GetVP();
12122
12123 m_b_rot_hidef = false;
12124
12125 ri = Image_Rotate(
12126 base_image, angle,
12127 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12128 m_b_rot_hidef, &m_roffset);
12129
12130 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12131 (rot_vp.rotation == VPoint.rotation) &&
12132 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12133 rot_vp.IsValid() && (ri.IsOk())) {
12134 b_rot_ok = true;
12135 }
12136 }
12137
12138 if (b_rot_ok) {
12139 delete m_prot_bm;
12140 m_prot_bm = new wxBitmap(ri);
12141 }
12142
12143 m_roffset.x += VPoint.rv_rect.x;
12144 m_roffset.y += VPoint.rv_rect.y;
12145 }
12146
12147 if (m_prot_bm && m_prot_bm->IsOk()) {
12148 rotd_dc.SelectObject(*m_prot_bm);
12149 pChartDC = &rotd_dc;
12150 } else {
12151 pChartDC = &temp_dc;
12152 m_roffset = wxPoint(0, 0);
12153 }
12154 } else { // unrotated
12155 pChartDC = &temp_dc;
12156 m_roffset = wxPoint(0, 0);
12157 }
12158
12159 wxPoint offset = m_roffset;
12160
12161 // Save the PixelCache viewpoint for next time
12162 m_cache_vp = VPoint;
12163
12164 // Set up a scratch DC for overlay objects
12165 wxMemoryDC mscratch_dc;
12166 mscratch_dc.SelectObject(*pscratch_bm);
12167
12168 mscratch_dc.ResetBoundingBox();
12169 mscratch_dc.DestroyClippingRegion();
12170 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12171
12172 // Blit the externally invalidated areas of the chart onto the scratch dc
12173 wxRegionIterator upd(rgn_blit); // get the update rect list
12174 while (upd) {
12175 wxRect rect = upd.GetRect();
12176
12177 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12178 rect.x - offset.x, rect.y - offset.y);
12179 upd++;
12180 }
12181
12182 // If multi-canvas, indicate which canvas has keyboard focus
12183 // by drawing a simple blue bar at the top.
12184 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12185 if (this == wxWindow::FindFocus()) {
12186 g_focusCanvas = this;
12187
12188 wxColour colour = GetGlobalColor("BLUE4");
12189 mscratch_dc.SetPen(wxPen(colour));
12190 mscratch_dc.SetBrush(wxBrush(colour));
12191
12192 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12193 mscratch_dc.DrawRectangle(activeRect);
12194 }
12195 }
12196
12197 // Any MBtiles?
12198 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12199 unsigned int im = stackIndexArray.size();
12200 if (VPoint.b_quilt && im > 0) {
12201 std::vector<int> tiles_to_show;
12202 for (unsigned int is = 0; is < im; is++) {
12203 const ChartTableEntry &cte =
12204 ChartData->GetChartTableEntry(stackIndexArray[is]);
12205 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12206 continue;
12207 }
12208 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12209 tiles_to_show.push_back(stackIndexArray[is]);
12210 }
12211 }
12212
12213 if (tiles_to_show.size())
12214 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12215 }
12216
12217 // May get an unexpected OnPaint call while switching display modes
12218 // Guard for that.
12219 if (!g_bopengl) {
12220 ocpnDC scratch_dc(mscratch_dc);
12221 RenderAlertMessage(mscratch_dc, GetVP());
12222 }
12223
12224#if 0
12225 // quiting?
12226 if (g_bquiting) {
12227#ifdef ocpnUSE_DIBSECTION
12228 ocpnMemDC q_dc;
12229#else
12230 wxMemoryDC q_dc;
12231#endif
12232 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12233 q_dc.SelectObject(qbm);
12234
12235 // Get a copy of the screen
12236 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12237
12238 // Draw a rectangle over the screen with a stipple brush
12239 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12240 q_dc.SetBrush(qbr);
12241 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12242
12243 // Blit back into source
12244 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12245 wxCOPY);
12246
12247 q_dc.SelectObject(wxNullBitmap);
12248 }
12249#endif
12250
12251#if 0
12252 // It is possible that this two-step method may be reuired for some platforms.
12253 // So, retain in the code base to aid recovery if necessary
12254
12255 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12256 if( VPoint.b_quilt ) {
12257 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12258 ChartBase *chart = m_pQuilt->GetRefChart();
12259 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12260
12261 // Clear the text Global declutter list
12262 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12263 if(ChPI)
12264 ChPI->ClearPLIBTextList();
12265 else{
12266 if(ps52plib)
12267 ps52plib->ClearTextList();
12268 }
12269
12270 wxMemoryDC t_dc;
12271 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12272
12273 wxColor maskBackground = wxColour(1,0,0);
12274 t_dc.SelectObject( qbm );
12275 t_dc.SetBackground(wxBrush(maskBackground));
12276 t_dc.Clear();
12277
12278 // Copy the scratch DC into the new bitmap
12279 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12280
12281 // Render the text to the new bitmap
12282 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12283 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12284
12285 // Copy the new bitmap back to the scratch dc
12286 wxRegionIterator upd_final( ru );
12287 while( upd_final ) {
12288 wxRect rect = upd_final.GetRect();
12289 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12290 upd_final++;
12291 }
12292
12293 t_dc.SelectObject( wxNullBitmap );
12294 }
12295 }
12296 }
12297#endif
12298 // Direct rendering model...
12299 if (VPoint.b_quilt) {
12300 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12301 ChartBase *chart = m_pQuilt->GetRefChart();
12302 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12303 // Clear the text Global declutter list
12304 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12305 if (ChPI)
12306 ChPI->ClearPLIBTextList();
12307 else {
12308 if (ps52plib) ps52plib->ClearTextList();
12309 }
12310
12311 // Render the text directly to the scratch bitmap
12312 OCPNRegion chart_all_text_region(
12313 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12314
12315 if (g_bShowChartBar && m_Piano) {
12316 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12317 GetVP().pix_width, m_Piano->GetHeight());
12318
12319 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12320 if (!style->chartStatusWindowTransparent)
12321 chart_all_text_region.Subtract(chart_bar_rect);
12322 }
12323
12324 if (m_Compass && m_Compass->IsShown()) {
12325 wxRect compassRect = m_Compass->GetRect();
12326 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12327 chart_all_text_region.Subtract(compassRect);
12328 }
12329 }
12330
12331 mscratch_dc.DestroyClippingRegion();
12332
12333 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12334 chart_all_text_region);
12335 }
12336 }
12337 }
12338
12339 // Now that charts are fully rendered, apply the overlay objects as decals.
12340 ocpnDC scratch_dc(mscratch_dc);
12341 DrawOverlayObjects(scratch_dc, ru);
12342
12343 // And finally, blit the scratch dc onto the physical dc
12344 wxRegionIterator upd_final(rgn_blit);
12345 while (upd_final) {
12346 wxRect rect = upd_final.GetRect();
12347 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12348 rect.y);
12349 upd_final++;
12350 }
12351
12352 // Deselect the chart bitmap from the temp_dc, so that it will not be
12353 // destroyed in the temp_dc dtor
12354 temp_dc.SelectObject(wxNullBitmap);
12355 // And for the scratch bitmap
12356 mscratch_dc.SelectObject(wxNullBitmap);
12357
12358 dc.DestroyClippingRegion();
12359
12360 PaintCleanup();
12361}
12362
12363void ChartCanvas::PaintCleanup() {
12364 // Handle the current graphic window, if present
12365 if (m_inPinch) return;
12366
12367 if (pCwin) {
12368 pCwin->Show();
12369 if (m_bTCupdate) {
12370 pCwin->Refresh();
12371 pCwin->Update();
12372 }
12373 }
12374
12375 // And set flags for next time
12376 m_bTCupdate = false;
12377
12378 // Handle deferred WarpPointer
12379 if (warp_flag) {
12380 WarpPointer(warp_x, warp_y);
12381 warp_flag = false;
12382 }
12383
12384 // Start movement timers, this runs nearly immediately.
12385 // the reason we cannot simply call it directly is the
12386 // refresh events it emits may be blocked from this paint event
12387 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12388 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12389}
12390
12391#if 0
12392wxColour GetErrorGraphicColor(double val)
12393{
12394 /*
12395 double valm = wxMin(val_max, val);
12396
12397 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12398 unsigned char red = (unsigned char)(255 * (valm/val_max));
12399
12400 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12401
12402 hv.saturation = 1.0;
12403 hv.value = 1.0;
12404
12405 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12406 return wxColour(rv.red, rv.green, rv.blue);
12407 */
12408
12409 // HTML colors taken from NOAA WW3 Web representation
12410 wxColour c;
12411 if((val > 0) && (val < 1)) c.Set("#002ad9");
12412 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12413 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12414 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12415 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12416 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12417 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12418 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12419 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12420 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12421 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12422 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12423 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12424 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12425 else if((val >= 30) && (val < 36)) c.Set("#870000");
12426 else if((val >= 36) && (val < 42)) c.Set("#690000");
12427 else if((val >= 42) && (val < 48)) c.Set("#550000");
12428 else if( val >= 48) c.Set("#410000");
12429
12430 return c;
12431}
12432
12433void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12434{
12435 wxImage gr_image(vp->pix_width, vp->pix_height);
12436 gr_image.InitAlpha();
12437
12438 double maxval = -10000;
12439 double minval = 10000;
12440
12441 double rlat, rlon;
12442 double glat, glon;
12443
12444 GetCanvasPixPoint(0, 0, rlat, rlon);
12445
12446 for(int i=1; i < vp->pix_height-1; i++)
12447 {
12448 for(int j=0; j < vp->pix_width; j++)
12449 {
12450 // Reference mercator value
12451// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12452
12453 // Georef value
12454 GetCanvasPixPoint(j, i, glat, glon);
12455
12456 maxval = wxMax(maxval, (glat - rlat));
12457 minval = wxMin(minval, (glat - rlat));
12458
12459 }
12460 rlat = glat;
12461 }
12462
12463 GetCanvasPixPoint(0, 0, rlat, rlon);
12464 for(int i=1; i < vp->pix_height-1; i++)
12465 {
12466 for(int j=0; j < vp->pix_width; j++)
12467 {
12468 // Reference mercator value
12469// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12470
12471 // Georef value
12472 GetCanvasPixPoint(j, i, glat, glon);
12473
12474 double f = ((glat - rlat)-minval)/(maxval - minval);
12475
12476 double dy = (f * 40);
12477
12478 wxColour c = GetErrorGraphicColor(dy);
12479 unsigned char r = c.Red();
12480 unsigned char g = c.Green();
12481 unsigned char b = c.Blue();
12482
12483 gr_image.SetRGB(j, i, r,g,b);
12484 if((glat - rlat )!= 0)
12485 gr_image.SetAlpha(j, i, 128);
12486 else
12487 gr_image.SetAlpha(j, i, 255);
12488
12489 }
12490 rlat = glat;
12491 }
12492
12493 // Create a Bitmap
12494 wxBitmap *pbm = new wxBitmap(gr_image);
12495 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12496 pbm->SetMask(gr_mask);
12497
12498 pmdc->DrawBitmap(*pbm, 0,0);
12499
12500 delete pbm;
12501
12502}
12503
12504#endif
12505
12506void ChartCanvas::CancelMouseRoute() {
12507 m_routeState = 0;
12508 m_pMouseRoute = NULL;
12509 m_bDrawingRoute = false;
12510}
12511
12512int ChartCanvas::GetNextContextMenuId() {
12513 return CanvasMenuHandler::GetNextContextMenuId();
12514}
12515
12516bool ChartCanvas::SetCursor(const wxCursor &c) {
12517#ifdef ocpnUSE_GL
12518 if (g_bopengl && m_glcc)
12519 return m_glcc->SetCursor(c);
12520 else
12521#endif
12522 return wxWindow::SetCursor(c);
12523}
12524
12525void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12526 if (g_bquiting) return;
12527 // Keep the mouse position members up to date
12528 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12529
12530 // Retrigger the route leg popup timer
12531 // This handles the case when the chart is moving in auto-follow mode,
12532 // but no user mouse input is made. The timer handler may Hide() the
12533 // popup if the chart moved enough n.b. We use slightly longer oneshot
12534 // value to allow this method's Refresh() to complete before potentially
12535 // getting another Refresh() in the popup timer handler.
12536 if (!m_RolloverPopupTimer.IsRunning() &&
12537 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12538 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12539 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12540 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12541
12542#ifdef ocpnUSE_GL
12543 if (m_glcc && g_bopengl) {
12544 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12545 // overlay objects.
12546 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12547
12548 m_glcc->Refresh(eraseBackground,
12549 NULL); // We always are going to render the entire screen
12550 // anyway, so make
12551 // sure that the window managers understand the invalid area
12552 // is actually the entire client area.
12553
12554 // We need to selectively Refresh some child windows, if they are visible.
12555 // Note that some children are refreshed elsewhere on timer ticks, so don't
12556 // need attention here.
12557
12558 // Thumbnail chart
12559 if (pthumbwin && pthumbwin->IsShown()) {
12560 pthumbwin->Raise();
12561 pthumbwin->Refresh(false);
12562 }
12563
12564 // ChartInfo window
12565 if (m_pCIWin && m_pCIWin->IsShown()) {
12566 m_pCIWin->Raise();
12567 m_pCIWin->Refresh(false);
12568 }
12569
12570 // if(g_MainToolbar)
12571 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12572
12573 } else
12574#endif
12575 wxWindow::Refresh(eraseBackground, rect);
12576}
12577
12578void ChartCanvas::Update() {
12579 if (m_glcc && g_bopengl) {
12580#ifdef ocpnUSE_GL
12581 m_glcc->Update();
12582#endif
12583 } else
12584 wxWindow::Update();
12585}
12586
12587void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12588 if (!pemboss) return;
12589 int x = pemboss->x, y = pemboss->y;
12590 const double factor = 200;
12591
12592 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12593 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12594 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12595
12596 // Grab a snipped image out of the chart
12597 wxMemoryDC snip_dc;
12598 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12599 snip_dc.SelectObject(snip_bmp);
12600
12601 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12602 snip_dc.SelectObject(wxNullBitmap);
12603
12604 wxImage snip_img = snip_bmp.ConvertToImage();
12605
12606 // Apply Emboss map to the snip image
12607 unsigned char *pdata = snip_img.GetData();
12608 if (pdata) {
12609 for (int y = 0; y < pemboss->height; y++) {
12610 int map_index = (y * pemboss->width);
12611 for (int x = 0; x < pemboss->width; x++) {
12612 double val = (pemboss->pmap[map_index] * factor) / 256.;
12613
12614 int nred = (int)((*pdata) + val);
12615 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12616 *pdata++ = (unsigned char)nred;
12617
12618 int ngreen = (int)((*pdata) + val);
12619 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12620 *pdata++ = (unsigned char)ngreen;
12621
12622 int nblue = (int)((*pdata) + val);
12623 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12624 *pdata++ = (unsigned char)nblue;
12625
12626 map_index++;
12627 }
12628 }
12629 }
12630
12631 // Convert embossed snip to a bitmap
12632 wxBitmap emb_bmp(snip_img);
12633
12634 // Map to another memoryDC
12635 wxMemoryDC result_dc;
12636 result_dc.SelectObject(emb_bmp);
12637
12638 // Blit to target
12639 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12640
12641 result_dc.SelectObject(wxNullBitmap);
12642}
12643
12644emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12645 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12646
12647 if (GetQuiltMode()) {
12648 // disable Overzoom indicator for MBTiles
12649 int refIndex = GetQuiltRefChartdbIndex();
12650 if (refIndex >= 0) {
12651 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12652 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12653 if (current_type == CHART_TYPE_MBTILES) {
12654 ChartBase *pChart = m_pQuilt->GetRefChart();
12655 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12656 if (ptc) {
12657 zoom_factor = ptc->GetZoomFactor();
12658 }
12659 }
12660 }
12661
12662 if (zoom_factor <= 3.9) return NULL;
12663 } else {
12664 if (m_singleChart) {
12665 if (zoom_factor <= 3.9) return NULL;
12666 } else
12667 return NULL;
12668 }
12669
12670 if (m_pEM_OverZoom) {
12671 m_pEM_OverZoom->x = 4;
12672 m_pEM_OverZoom->y = 0;
12673 if (g_MainToolbar && IsPrimaryCanvas()) {
12674 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12675 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12676 }
12677 }
12678 return m_pEM_OverZoom;
12679}
12680
12681void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12682 GridDraw(dc);
12683
12684 // bool pluginOverlayRender = true;
12685 //
12686 // if(g_canvasConfig > 0){ // Multi canvas
12687 // if(IsPrimaryCanvas())
12688 // pluginOverlayRender = false;
12689 // }
12690
12691 g_overlayCanvas = this;
12692
12693 if (g_pi_manager) {
12694 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12695 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12697 }
12698
12699 AISDrawAreaNotices(dc, GetVP(), this);
12700
12701 wxDC *pdc = dc.GetDC();
12702 if (pdc) {
12703 pdc->DestroyClippingRegion();
12704 wxDCClipper(*pdc, ru);
12705 }
12706
12707 if (m_bShowNavobjects) {
12708 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12709 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12710 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12711 DrawAnchorWatchPoints(dc);
12712 } else {
12713 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12714 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12715 }
12716
12717 AISDraw(dc, GetVP(), this);
12718 ShipDraw(dc);
12719 AlertDraw(dc);
12720
12721 RenderVisibleSectorLights(dc);
12722
12723 RenderAllChartOutlines(dc, GetVP());
12724 RenderRouteLegs(dc);
12725 RenderShipToActive(dc, false);
12726 ScaleBarDraw(dc);
12727 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12728 if (g_pi_manager) {
12729 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12731 }
12732
12733 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12734 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12735
12736 if (g_pi_manager) {
12737 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12739 }
12740
12741 if (m_bShowTide) {
12742 RebuildTideSelectList(GetVP().GetBBox());
12743 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12744 }
12745
12746 if (m_bShowCurrent) {
12747 RebuildCurrentSelectList(GetVP().GetBBox());
12748 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12749 }
12750
12751 if (!g_PrintingInProgress) {
12752 if (IsPrimaryCanvas()) {
12753 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12754 }
12755
12756 if (IsPrimaryCanvas()) {
12757 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12758 }
12759
12760 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12761
12762 if (m_pTrackRolloverWin) {
12763 m_pTrackRolloverWin->Draw(dc);
12764 m_brepaint_piano = true;
12765 }
12766
12767 if (m_pRouteRolloverWin) {
12768 m_pRouteRolloverWin->Draw(dc);
12769 m_brepaint_piano = true;
12770 }
12771
12772 if (m_pAISRolloverWin) {
12773 m_pAISRolloverWin->Draw(dc);
12774 m_brepaint_piano = true;
12775 }
12776 if (m_brepaint_piano && g_bShowChartBar) {
12777 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12778 }
12779
12780 if (m_Compass) m_Compass->Paint(dc);
12781
12782 if (!g_CanvasHideNotificationIcon) {
12783 if (IsPrimaryCanvas()) {
12784 auto &noteman = NotificationManager::GetInstance();
12785 if (noteman.GetNotificationCount()) {
12786 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12787 if (m_notification_button->UpdateStatus()) Refresh();
12788 m_notification_button->Show(true);
12789 m_notification_button->Paint(dc);
12790 } else {
12791 m_notification_button->Show(false);
12792 }
12793 }
12794 }
12795 }
12796 if (g_pi_manager) {
12797 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12799 }
12800}
12801
12802emboss_data *ChartCanvas::EmbossDepthScale() {
12803 if (!m_bShowDepthUnits) return NULL;
12804
12805 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12806
12807 if (GetQuiltMode()) {
12808 wxString s = m_pQuilt->GetQuiltDepthUnit();
12809 s.MakeUpper();
12810 if (s == "FEET")
12811 depth_unit_type = DEPTH_UNIT_FEET;
12812 else if (s.StartsWith("FATHOMS"))
12813 depth_unit_type = DEPTH_UNIT_FATHOMS;
12814 else if (s.StartsWith("METERS"))
12815 depth_unit_type = DEPTH_UNIT_METERS;
12816 else if (s.StartsWith("METRES"))
12817 depth_unit_type = DEPTH_UNIT_METERS;
12818 else if (s.StartsWith("METRIC"))
12819 depth_unit_type = DEPTH_UNIT_METERS;
12820 else if (s.StartsWith("METER"))
12821 depth_unit_type = DEPTH_UNIT_METERS;
12822
12823 } else {
12824 if (m_singleChart) {
12825 depth_unit_type = m_singleChart->GetDepthUnitType();
12826 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12827 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12828 }
12829 }
12830
12831 emboss_data *ped = NULL;
12832 switch (depth_unit_type) {
12833 case DEPTH_UNIT_FEET:
12834 ped = m_pEM_Feet;
12835 break;
12836 case DEPTH_UNIT_METERS:
12837 ped = m_pEM_Meters;
12838 break;
12839 case DEPTH_UNIT_FATHOMS:
12840 ped = m_pEM_Fathoms;
12841 break;
12842 default:
12843 return NULL;
12844 }
12845
12846 ped->x = (GetVP().pix_width - ped->width);
12847
12848 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12849 wxRect r = m_Compass->GetRect();
12850 ped->y = r.y + r.height;
12851 } else {
12852 ped->y = 40;
12853 }
12854 return ped;
12855}
12856
12857void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12858 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12859 wxFont font;
12860 if (style->embossFont == wxEmptyString) {
12861 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12862 font = *dFont;
12863 font.SetPointSize(60);
12864 font.SetWeight(wxFONTWEIGHT_BOLD);
12865 } else
12866 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12867 wxFONTWEIGHT_BOLD, false, style->embossFont);
12868
12869 int emboss_width = 500;
12870 int emboss_height = 200;
12871
12872 // Free any existing emboss maps
12873 delete m_pEM_Feet;
12874 delete m_pEM_Meters;
12875 delete m_pEM_Fathoms;
12876
12877 // Create the 3 DepthUnit emboss map structures
12878 m_pEM_Feet =
12879 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12880 m_pEM_Meters =
12881 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12882 m_pEM_Fathoms =
12883 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12884}
12885
12886#define OVERZOOM_TEXT _("OverZoom")
12887
12888void ChartCanvas::SetOverzoomFont() {
12889 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12890 int w, h;
12891
12892 wxFont font;
12893 if (style->embossFont == wxEmptyString) {
12894 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12895 font = *dFont;
12896 font.SetPointSize(40);
12897 font.SetWeight(wxFONTWEIGHT_BOLD);
12898 } else
12899 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12900 wxFONTWEIGHT_BOLD, false, style->embossFont);
12901
12902 wxClientDC dc(this);
12903 dc.SetFont(font);
12904 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12905
12906 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12907 font.SetPointSize(font.GetPointSize() - 1);
12908 dc.SetFont(font);
12909 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12910 }
12911 m_overzoomFont = font;
12912 m_overzoomTextWidth = w;
12913 m_overzoomTextHeight = h;
12914}
12915
12916void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12917 delete m_pEM_OverZoom;
12918
12919 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12920 m_pEM_OverZoom =
12921 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12922 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12923}
12924
12925emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12926 int height, const wxString &str,
12927 ColorScheme cs) {
12928 int *pmap;
12929
12930 // Create a temporary bitmap
12931 wxBitmap bmp(width, height, -1);
12932
12933 // Create a memory DC
12934 wxMemoryDC temp_dc;
12935 temp_dc.SelectObject(bmp);
12936
12937 // Paint on it
12938 temp_dc.SetBackground(*wxWHITE_BRUSH);
12939 temp_dc.SetTextBackground(*wxWHITE);
12940 temp_dc.SetTextForeground(*wxBLACK);
12941
12942 temp_dc.Clear();
12943
12944 temp_dc.SetFont(font);
12945
12946 int str_w, str_h;
12947 temp_dc.GetTextExtent(str, &str_w, &str_h);
12948 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12949 temp_dc.DrawText(str, 1, 1);
12950
12951 // Deselect the bitmap
12952 temp_dc.SelectObject(wxNullBitmap);
12953
12954 // Convert bitmap the wxImage for manipulation
12955 wxImage img = bmp.ConvertToImage();
12956
12957 int image_width = str_w * 105 / 100;
12958 int image_height = str_h * 105 / 100;
12959 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12960 wxMin(image_height, img.GetHeight()));
12961 wxImage imgs = img.GetSubImage(r);
12962
12963 double val_factor;
12964 switch (cs) {
12965 case GLOBAL_COLOR_SCHEME_DAY:
12966 default:
12967 val_factor = 1;
12968 break;
12969 case GLOBAL_COLOR_SCHEME_DUSK:
12970 val_factor = .5;
12971 break;
12972 case GLOBAL_COLOR_SCHEME_NIGHT:
12973 val_factor = .25;
12974 break;
12975 }
12976
12977 int val;
12978 int index;
12979 const int w = imgs.GetWidth();
12980 const int h = imgs.GetHeight();
12981 pmap = (int *)calloc(w * h * sizeof(int), 1);
12982 // Create emboss map by differentiating the emboss image
12983 // and storing integer results in pmap
12984 // n.b. since the image is B/W, it is sufficient to check
12985 // one channel (i.e. red) only
12986 for (int y = 1; y < h - 1; y++) {
12987 for (int x = 1; x < w - 1; x++) {
12988 val =
12989 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
12990 val = (int)(val * val_factor);
12991 index = (y * w) + x;
12992 pmap[index] = val;
12993 }
12994 }
12995
12996 emboss_data *pret = new emboss_data;
12997 pret->pmap = pmap;
12998 pret->width = w;
12999 pret->height = h;
13000
13001 return pret;
13002}
13003
13004void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13005 Track *active_track = NULL;
13006 for (Track *pTrackDraw : g_TrackList) {
13007 if (g_pActiveTrack == pTrackDraw) {
13008 active_track = pTrackDraw;
13009 continue;
13010 }
13011
13012 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13013 }
13014
13015 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13016}
13017
13018void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13019 Track *active_track = NULL;
13020 for (Track *pTrackDraw : g_TrackList) {
13021 if (g_pActiveTrack == pTrackDraw) {
13022 active_track = pTrackDraw;
13023 break;
13024 }
13025 }
13026 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13027}
13028
13029void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13030 Route *active_route = NULL;
13031 for (Route *pRouteDraw : *pRouteList) {
13032 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13033 active_route = pRouteDraw;
13034 continue;
13035 }
13036
13037 // if(m_canvasIndex == 1)
13038 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13039 }
13040
13041 // Draw any active or selected route (or track) last, so that is is always on
13042 // top
13043 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13044}
13045
13046void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13047 Route *active_route = NULL;
13048
13049 for (Route *pRouteDraw : *pRouteList) {
13050 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13051 active_route = pRouteDraw;
13052 break;
13053 }
13054 }
13055 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13056}
13057
13058void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13059 if (!pWayPointMan) return;
13060
13061 auto node = pWayPointMan->GetWaypointList()->begin();
13062
13063 while (node != pWayPointMan->GetWaypointList()->end()) {
13064 RoutePoint *pWP = *node;
13065 if (pWP) {
13066 if (pWP->m_bIsInRoute) {
13067 ++node;
13068 continue;
13069 }
13070
13071 /* technically incorrect... waypoint has bounding box */
13072 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13073 RoutePointGui(*pWP).Draw(dc, this, NULL);
13074 else {
13075 // Are Range Rings enabled?
13076 if (pWP->GetShowWaypointRangeRings() &&
13077 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13078 double factor = 1.00;
13079 if (pWP->GetWaypointRangeRingsStepUnits() ==
13080 1) // convert kilometers to NMi
13081 factor = 1 / 1.852;
13082
13083 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13084 pWP->GetWaypointRangeRingsStep() / 60.;
13085 radius *= 2; // Fudge factor
13086
13087 LLBBox radar_box;
13088 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13089 pWP->m_lat + radius, pWP->m_lon + radius);
13090 if (!BltBBox.IntersectOut(radar_box)) {
13091 RoutePointGui(*pWP).Draw(dc, this, NULL);
13092 }
13093 }
13094 }
13095 }
13096
13097 ++node;
13098 }
13099}
13100
13101void ChartCanvas::DrawBlinkObjects() {
13102 // All RoutePoints
13103 wxRect update_rect;
13104
13105 if (!pWayPointMan) return;
13106
13107 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13108 if (pWP) {
13109 if (pWP->m_bBlink) {
13110 update_rect.Union(pWP->CurrentRect_in_DC);
13111 }
13112 }
13113 }
13114 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13115}
13116
13117void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13118 // draw anchor watch rings, if activated
13119
13121 wxPoint r1, r2;
13122 wxPoint lAnchorPoint1, lAnchorPoint2;
13123 double lpp1 = 0.0;
13124 double lpp2 = 0.0;
13125 if (pAnchorWatchPoint1) {
13126 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13128 &lAnchorPoint1);
13129 }
13130 if (pAnchorWatchPoint2) {
13131 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13133 &lAnchorPoint2);
13134 }
13135
13136 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13137 wxPen ppPenr(GetGlobalColor("URED"), 2);
13138
13139 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13140 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13141 dc.SetBrush(*ppBrush);
13142
13143 if (lpp1 > 0) {
13144 dc.SetPen(ppPeng);
13145 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13146 }
13147
13148 if (lpp2 > 0) {
13149 dc.SetPen(ppPeng);
13150 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13151 }
13152
13153 if (lpp1 < 0) {
13154 dc.SetPen(ppPenr);
13155 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13156 }
13157
13158 if (lpp2 < 0) {
13159 dc.SetPen(ppPenr);
13160 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13161 }
13162 }
13163}
13164
13165double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13166 double lpp = 0.;
13167 wxPoint r1;
13168 wxPoint lAnchorPoint;
13169 double d1 = 0.0;
13170 double dabs;
13171 double tlat1, tlon1;
13172
13173 if (pAnchorWatchPoint) {
13174 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13175 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13176 dabs = fabs(d1 / 1852.);
13177 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13178 &tlat1, &tlon1);
13179 GetCanvasPointPix(tlat1, tlon1, &r1);
13180 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13181 &lAnchorPoint);
13182 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13183 pow((double)(lAnchorPoint.y - r1.y), 2));
13184
13185 // This is an entry watch
13186 if (d1 < 0) lpp = -lpp;
13187 }
13188 return lpp;
13189}
13190
13191//------------------------------------------------------------------------------------------
13192// Tides Support
13193//------------------------------------------------------------------------------------------
13194void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13195 if (!ptcmgr) return;
13196
13197 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13198
13199 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13200 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13201 double lon = pIDX->IDX_lon;
13202 double lat = pIDX->IDX_lat;
13203
13204 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13205 if ((type == 't') || (type == 'T')) {
13206 if (BBox.Contains(lat, lon)) {
13207 // Manage the point selection list
13208 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13209 }
13210 }
13211 }
13212}
13213
13214void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13215 if (!ptcmgr) return;
13216
13217 wxDateTime this_now = gTimeSource;
13218 bool cur_time = !gTimeSource.IsValid();
13219 if (cur_time) this_now = wxDateTime::Now();
13220 time_t t_this_now = this_now.GetTicks();
13221
13222 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13223 wxPENSTYLE_SOLID);
13224 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13225 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13226 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13227 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13228
13229 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13230 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13231 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13232 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13233 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13234 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13235
13236 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13237 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13238 int font_size = wxMax(10, dFont->GetPointSize());
13239 font_size /= g_Platform->GetDisplayDIPMult(this);
13240 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13241 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13242 false, dFont->GetFaceName());
13243
13244 dc.SetPen(*pblack_pen);
13245 dc.SetBrush(*pgreen_brush);
13246
13247 wxBitmap bm;
13248 switch (m_cs) {
13249 case GLOBAL_COLOR_SCHEME_DAY:
13250 bm = m_bmTideDay;
13251 break;
13252 case GLOBAL_COLOR_SCHEME_DUSK:
13253 bm = m_bmTideDusk;
13254 break;
13255 case GLOBAL_COLOR_SCHEME_NIGHT:
13256 bm = m_bmTideNight;
13257 break;
13258 default:
13259 bm = m_bmTideDay;
13260 break;
13261 }
13262
13263 int bmw = bm.GetWidth();
13264 int bmh = bm.GetHeight();
13265
13266 float scale_factor = 1.0;
13267
13268 // Set the onscreen size of the symbol
13269 // Compensate for various display resolutions
13270 float icon_pixelRefDim = 45;
13271
13272 // Tidal report graphic is scaled by the text size of the label in use
13273 wxScreenDC sdc;
13274 int height;
13275 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13276 height *= g_Platform->GetDisplayDIPMult(this);
13277 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13278
13279 scale_factor *= pix_factor;
13280
13281 float user_scale_factor = g_ChartScaleFactorExp;
13282 if (g_ChartScaleFactorExp > 1.0)
13283 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13284 1.2; // soften the scale factor a bit
13285
13286 scale_factor *= user_scale_factor;
13287 scale_factor *= GetContentScaleFactor();
13288
13289 {
13290 double marge = 0.05;
13291 std::vector<LLBBox> drawn_boxes;
13292 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13293 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13294
13295 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13296 if ((type == 't') || (type == 'T')) // only Tides
13297 {
13298 double lon = pIDX->IDX_lon;
13299 double lat = pIDX->IDX_lat;
13300
13301 if (BBox.ContainsMarge(lat, lon, marge)) {
13302 // Avoid drawing detailed graphic for duplicate tide stations
13303 if (GetVP().chart_scale < 500000) {
13304 bool bdrawn = false;
13305 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13306 if (drawn_boxes[i].Contains(lat, lon)) {
13307 bdrawn = true;
13308 break;
13309 }
13310 }
13311 if (bdrawn) continue; // the station loop
13312
13313 LLBBox this_box;
13314 this_box.Set(lat, lon, lat, lon);
13315 this_box.EnLarge(.005);
13316 drawn_boxes.push_back(this_box);
13317 }
13318
13319 wxPoint r;
13320 GetCanvasPointPix(lat, lon, &r);
13321 // draw standard icons
13322 if (GetVP().chart_scale > 500000) {
13323 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13324 }
13325 // draw "extended" icons
13326 else {
13327 dc.SetFont(*plabelFont);
13328 {
13329 {
13330 float val, nowlev;
13331 float ltleve = 0.;
13332 float htleve = 0.;
13333 time_t tctime;
13334 time_t lttime = 0;
13335 time_t httime = 0;
13336 bool wt;
13337 // define if flood or ebb in the last ten minutes and verify if
13338 // data are useable
13339 if (ptcmgr->GetTideFlowSens(
13340 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13341 pIDX->IDX_rec_num, nowlev, val, wt)) {
13342 // search forward the first HW or LW near "now" ( starting at
13343 // "now" - ten minutes )
13344 ptcmgr->GetHightOrLowTide(
13345 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13346 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13347 wt, pIDX->IDX_rec_num, val, tctime);
13348 if (wt) {
13349 httime = tctime;
13350 htleve = val;
13351 } else {
13352 lttime = tctime;
13353 ltleve = val;
13354 }
13355 wt = !wt;
13356
13357 // then search opposite tide near "now"
13358 if (tctime > t_this_now) // search backward
13359 ptcmgr->GetHightOrLowTide(
13360 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13361 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13362 pIDX->IDX_rec_num, val, tctime);
13363 else
13364 // or search forward
13365 ptcmgr->GetHightOrLowTide(
13366 t_this_now, FORWARD_TEN_MINUTES_STEP,
13367 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13368 val, tctime);
13369 if (wt) {
13370 httime = tctime;
13371 htleve = val;
13372 } else {
13373 lttime = tctime;
13374 ltleve = val;
13375 }
13376
13377 // draw the tide rectangle:
13378
13379 // tide icon rectangle has default pre-scaled width = 12 ,
13380 // height = 45
13381 int width = (int)(12 * scale_factor + 0.5);
13382 int height = (int)(45 * scale_factor + 0.5);
13383 int linew = wxMax(1, (int)(scale_factor));
13384 int xDraw = r.x - (width / 2);
13385 int yDraw = r.y - (height / 2);
13386
13387 // process tide state ( %height and flow sens )
13388 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13389 int hs = (httime > lttime) ? -4 : 4;
13390 hs *= (int)(scale_factor + 0.5);
13391 if (ts > 0.995 || ts < 0.005) hs = 0;
13392 int ht_y = (int)(height * ts);
13393
13394 // draw yellow tide rectangle outlined in black
13395 pblack_pen->SetWidth(linew);
13396 dc.SetPen(*pblack_pen);
13397 dc.SetBrush(*pyelo_brush);
13398 dc.DrawRectangle(xDraw, yDraw, width, height);
13399
13400 // draw blue rectangle as water height, smaller in width than
13401 // yellow rectangle
13402 dc.SetPen(*pblue_pen);
13403 dc.SetBrush(*pblue_brush);
13404 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13405 (width - (4 * linew)), height - ht_y);
13406
13407 // draw sens arrows (ensure they are not "under-drawn" by top
13408 // line of blue rectangle )
13409 int hl;
13410 wxPoint arrow[3];
13411 arrow[0].x = xDraw + 2 * linew;
13412 arrow[1].x = xDraw + width / 2;
13413 arrow[2].x = xDraw + width - 2 * linew;
13414 pyelo_pen->SetWidth(linew);
13415 pblue_pen->SetWidth(linew);
13416 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13417 {
13418 hl = (int)(height * 0.25) + yDraw;
13419 arrow[0].y = hl;
13420 arrow[1].y = hl + hs;
13421 arrow[2].y = hl;
13422 if (ts < 0.15)
13423 dc.SetPen(*pyelo_pen);
13424 else
13425 dc.SetPen(*pblue_pen);
13426 dc.DrawLines(3, arrow);
13427 }
13428 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13429 {
13430 hl = (int)(height * 0.5) + yDraw;
13431 arrow[0].y = hl;
13432 arrow[1].y = hl + hs;
13433 arrow[2].y = hl;
13434 if (ts < 0.40)
13435 dc.SetPen(*pyelo_pen);
13436 else
13437 dc.SetPen(*pblue_pen);
13438 dc.DrawLines(3, arrow);
13439 }
13440 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13441 {
13442 hl = (int)(height * 0.75) + yDraw;
13443 arrow[0].y = hl;
13444 arrow[1].y = hl + hs;
13445 arrow[2].y = hl;
13446 if (ts < 0.65)
13447 dc.SetPen(*pyelo_pen);
13448 else
13449 dc.SetPen(*pblue_pen);
13450 dc.DrawLines(3, arrow);
13451 }
13452 // draw tide level text
13453 wxString s;
13454 s.Printf("%3.1f", nowlev);
13455 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13456 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13457 int wx1;
13458 dc.GetTextExtent(s, &wx1, NULL);
13459 wx1 *= g_Platform->GetDisplayDIPMult(this);
13460 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13461 }
13462 }
13463 }
13464 }
13465 }
13466 }
13467 }
13468 }
13469}
13470
13471//------------------------------------------------------------------------------------------
13472// Currents Support
13473//------------------------------------------------------------------------------------------
13474
13475void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13476 if (!ptcmgr) return;
13477
13478 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13479
13480 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13481 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13482 double lon = pIDX->IDX_lon;
13483 double lat = pIDX->IDX_lat;
13484
13485 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13486 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13487 if ((BBox.Contains(lat, lon))) {
13488 // Manage the point selection list
13489 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13490 }
13491 }
13492 }
13493}
13494
13495void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13496 if (!ptcmgr) return;
13497
13498 float tcvalue, dir;
13499 bool bnew_val;
13500 char sbuf[20];
13501 wxFont *pTCFont;
13502 double lon_last = 0.;
13503 double lat_last = 0.;
13504 // arrow size for Raz Blanchard : 12 knots north
13505 double marge = 0.2;
13506 bool cur_time = !gTimeSource.IsValid();
13507
13508 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13509 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13510
13511 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13512 wxPENSTYLE_SOLID);
13513 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13514 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13515 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13516 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13517 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13518 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13519 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13520 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13521
13522 double skew_angle = GetVPRotation();
13523
13524 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13525 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13526 int font_size = wxMax(10, dFont->GetPointSize());
13527 font_size /= g_Platform->GetDisplayDIPMult(this);
13528 pTCFont = FontMgr::Get().FindOrCreateFont(
13529 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13530 false, dFont->GetFaceName());
13531
13532 float scale_factor = 1.0;
13533
13534 // Set the onscreen size of the symbol
13535 // Current report graphic is scaled by the text size of the label in use
13536 wxScreenDC sdc;
13537 int height;
13538 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13539 height *= g_Platform->GetDisplayDIPMult(this);
13540 float nominal_icon_size_pixels = 15;
13541 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13542
13543 scale_factor *= pix_factor;
13544
13545 float user_scale_factor = g_ChartScaleFactorExp;
13546 if (g_ChartScaleFactorExp > 1.0)
13547 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13548 1.2; // soften the scale factor a bit
13549
13550 scale_factor *= user_scale_factor;
13551
13552 scale_factor *= GetContentScaleFactor();
13553
13554 {
13555 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13556 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13557 double lon = pIDX->IDX_lon;
13558 double lat = pIDX->IDX_lat;
13559
13560 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13561 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13562 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13563 wxPoint r;
13564 GetCanvasPointPix(lat, lon, &r);
13565
13566 wxPoint d[4]; // points of a diamond at the current station location
13567 int dd = (int)(5.0 * scale_factor + 0.5);
13568 d[0].x = r.x;
13569 d[0].y = r.y + dd;
13570 d[1].x = r.x + dd;
13571 d[1].y = r.y;
13572 d[2].x = r.x;
13573 d[2].y = r.y - dd;
13574 d[3].x = r.x - dd;
13575 d[3].y = r.y;
13576
13577 if (1) {
13578 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13579 dc.SetPen(*pblack_pen);
13580 dc.SetBrush(*porange_brush);
13581 dc.DrawPolygon(4, d);
13582
13583 if (type == 'C') {
13584 dc.SetBrush(*pblack_brush);
13585 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13586 }
13587
13588 if (GetVP().chart_scale < 1000000) {
13589 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13590 continue;
13591 } else
13592 continue;
13593
13594 if (1 /*type == 'c'*/) {
13595 {
13596 // Get the display pixel location of the current station
13597 int pixxc, pixyc;
13598 pixxc = r.x;
13599 pixyc = r.y;
13600
13601 // Adjust drawing size using logarithmic scale. tcvalue is
13602 // current in knots
13603 double a1 = fabs(tcvalue) * 10.;
13604 // Current values <= 0.1 knot will have no arrow
13605 a1 = wxMax(1.0, a1);
13606 double a2 = log10(a1);
13607
13608 float cscale = scale_factor * a2 * 0.3;
13609
13610 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13611 dc.SetPen(*porange_pen);
13612 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13613 cscale);
13614 // Draw text, if enabled
13615
13616 if (bDrawCurrentValues) {
13617 dc.SetFont(*pTCFont);
13618 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13619 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13620 }
13621 }
13622 } // scale
13623 }
13624 /* This is useful for debugging the TC database
13625 else
13626 {
13627 dc.SetPen ( *porange_pen );
13628 dc.SetBrush ( *pgray_brush );
13629 dc.DrawPolygon ( 4, d );
13630 }
13631 */
13632 }
13633 lon_last = lon;
13634 lat_last = lat;
13635 }
13636 }
13637 }
13638}
13639
13640void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13641 ShowSingleTideDialog(x, y, pvIDX);
13642}
13643
13644void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13645 if (!pvIDX) return; // Validate input
13646
13647 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13648
13649 // Check if a tide dialog is already open and visible
13650 if (pCwin && pCwin->IsShown()) {
13651 // Same tide station: bring existing dialog to front (preserves user
13652 // context)
13653 if (pCwin->GetCurrentIDX() == pNewIDX) {
13654 pCwin->Raise();
13655 pCwin->SetFocus();
13656
13657 // Provide subtle visual feedback that dialog is already open
13658 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13659 return;
13660 }
13661
13662 // Different tide station: close current dialog before opening new one
13663 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13664 }
13665
13666 if (pCwin) {
13667 // This shouldn't happen but ensures clean state
13668 pCwin->Destroy();
13669 pCwin = NULL;
13670 }
13671
13672 // Create and display new tide dialog
13673 pCwin = new TCWin(this, x, y, pvIDX);
13674
13675 // Ensure the dialog is properly shown and focused
13676 if (pCwin) {
13677 pCwin->Show();
13678 pCwin->Raise();
13679 pCwin->SetFocus();
13680 }
13681}
13682
13683bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13684
13686 if (pCwin) {
13687 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13688 }
13689}
13690
13691#define NUM_CURRENT_ARROW_POINTS 9
13692static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13693 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13694 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13695 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13696
13697void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13698 double scale) {
13699 if (scale > 1e-2) {
13700 float sin_rot = sin(rot_angle * PI / 180.);
13701 float cos_rot = cos(rot_angle * PI / 180.);
13702
13703 // Move to the first point
13704
13705 float xt = CurrentArrowArray[0].x;
13706 float yt = CurrentArrowArray[0].y;
13707
13708 float xp = (xt * cos_rot) - (yt * sin_rot);
13709 float yp = (xt * sin_rot) + (yt * cos_rot);
13710 int x1 = (int)(xp * scale);
13711 int y1 = (int)(yp * scale);
13712
13713 // Walk thru the point list
13714 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13715 xt = CurrentArrowArray[ip].x;
13716 yt = CurrentArrowArray[ip].y;
13717
13718 float xp = (xt * cos_rot) - (yt * sin_rot);
13719 float yp = (xt * sin_rot) + (yt * cos_rot);
13720 int x2 = (int)(xp * scale);
13721 int y2 = (int)(yp * scale);
13722
13723 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13724
13725 x1 = x2;
13726 y1 = y2;
13727 }
13728 }
13729}
13730
13731wxString ChartCanvas::FindValidUploadPort() {
13732 wxString port;
13733 // Try to use the saved persistent upload port first
13734 if (!g_uploadConnection.IsEmpty() &&
13735 g_uploadConnection.StartsWith("Serial")) {
13736 port = g_uploadConnection;
13737 }
13738
13739 else {
13740 // If there is no persistent upload port recorded (yet)
13741 // then use the first available serial connection which has output defined.
13742 for (auto *cp : TheConnectionParams()) {
13743 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13744 port << "Serial:" << cp->Port;
13745 }
13746 }
13747 return port;
13748}
13749
13750void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13751 if (!win) return;
13752
13753 if (NULL == g_pais_query_dialog_active) {
13754 int pos_x = g_ais_query_dialog_x;
13755 int pos_y = g_ais_query_dialog_y;
13756
13757 if (g_pais_query_dialog_active) {
13758 g_pais_query_dialog_active->Destroy();
13759 g_pais_query_dialog_active = new AISTargetQueryDialog();
13760 } else {
13761 g_pais_query_dialog_active = new AISTargetQueryDialog();
13762 }
13763
13764 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13765 wxPoint(pos_x, pos_y));
13766
13767 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13768 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13769 g_pais_query_dialog_active->SetMMSI(mmsi);
13770 g_pais_query_dialog_active->UpdateText();
13771 wxSize sz = g_pais_query_dialog_active->GetSize();
13772
13773 bool b_reset_pos = false;
13774#ifdef __WXMSW__
13775 // Support MultiMonitor setups which an allow negative window positions.
13776 // If the requested window title bar does not intersect any installed
13777 // monitor, then default to simple primary monitor positioning.
13778 RECT frame_title_rect;
13779 frame_title_rect.left = pos_x;
13780 frame_title_rect.top = pos_y;
13781 frame_title_rect.right = pos_x + sz.x;
13782 frame_title_rect.bottom = pos_y + 30;
13783
13784 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13785 b_reset_pos = true;
13786#else
13787
13788 // Make sure drag bar (title bar) of window intersects wxClient Area of
13789 // screen, with a little slop...
13790 wxRect window_title_rect; // conservative estimate
13791 window_title_rect.x = pos_x;
13792 window_title_rect.y = pos_y;
13793 window_title_rect.width = sz.x;
13794 window_title_rect.height = 30;
13795
13796 wxRect ClientRect = wxGetClientDisplayRect();
13797 ClientRect.Deflate(
13798 60, 60); // Prevent the new window from being too close to the edge
13799 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13800
13801#endif
13802
13803 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13804
13805 } else {
13806 g_pais_query_dialog_active->SetMMSI(mmsi);
13807 g_pais_query_dialog_active->UpdateText();
13808 }
13809
13810 g_pais_query_dialog_active->Show();
13811}
13812
13813void ChartCanvas::ToggleCanvasQuiltMode() {
13814 bool cur_mode = GetQuiltMode();
13815
13816 if (!GetQuiltMode())
13817 SetQuiltMode(true);
13818 else if (GetQuiltMode()) {
13819 SetQuiltMode(false);
13820 g_sticky_chart = GetQuiltReferenceChartIndex();
13821 }
13822
13823 if (cur_mode != GetQuiltMode()) {
13824 SetupCanvasQuiltMode();
13825 DoCanvasUpdate();
13826 InvalidateGL();
13827 Refresh();
13828 }
13829 // TODO What to do about this?
13830 // g_bQuiltEnable = GetQuiltMode();
13831
13832 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13833 if (ps52plib) ps52plib->GenerateStateHash();
13834
13835 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13836 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13837}
13838
13839void ChartCanvas::DoCanvasStackDelta(int direction) {
13840 if (!GetQuiltMode()) {
13841 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13842 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13843 if ((current_stack_index + direction) < 0) return;
13844
13845 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13846 int new_dbIndex =
13847 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13848
13849 if (IsChartQuiltableRef(new_dbIndex)) {
13850 ToggleCanvasQuiltMode();
13851 SelectQuiltRefdbChart(new_dbIndex);
13852 m_bpersistent_quilt = false;
13853 }
13854 } else {
13855 SelectChartFromStack(current_stack_index + direction);
13856 }
13857 } else {
13858 std::vector<int> piano_chart_index_array =
13859 GetQuiltExtendedStackdbIndexArray();
13860 int refdb = GetQuiltRefChartdbIndex();
13861
13862 // Find the ref chart in the stack
13863 int current_index = -1;
13864 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13865 if (refdb == piano_chart_index_array[i]) {
13866 current_index = i;
13867 break;
13868 }
13869 }
13870 if (current_index == -1) return;
13871
13872 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13873 int target_family = ctet.GetChartFamily();
13874
13875 int new_index = -1;
13876 int check_index = current_index + direction;
13877 bool found = false;
13878 int check_dbIndex = -1;
13879 int new_dbIndex = -1;
13880
13881 // When quilted. switch within the same chart family
13882 while (!found &&
13883 (unsigned int)check_index < piano_chart_index_array.size() &&
13884 (check_index >= 0)) {
13885 check_dbIndex = piano_chart_index_array[check_index];
13886 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13887 if (target_family == cte.GetChartFamily()) {
13888 found = true;
13889 new_index = check_index;
13890 new_dbIndex = check_dbIndex;
13891 break;
13892 }
13893
13894 check_index += direction;
13895 }
13896
13897 if (!found) return;
13898
13899 if (!IsChartQuiltableRef(new_dbIndex)) {
13900 ToggleCanvasQuiltMode();
13901 SelectdbChart(new_dbIndex);
13902 m_bpersistent_quilt = true;
13903 } else {
13904 SelectQuiltRefChart(new_index);
13905 }
13906 }
13907
13908 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13909 // (checkmarks etc)
13910 SetQuiltChartHiLiteIndex(-1);
13911
13912 ReloadVP();
13913}
13914
13915//--------------------------------------------------------------------------------------------------------
13916//
13917// Toolbar support
13918//
13919//--------------------------------------------------------------------------------------------------------
13920
13921void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13922 // Handle the per-canvas toolbar clicks here
13923
13924 switch (event.GetId()) {
13925 case ID_ZOOMIN: {
13926 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13927 break;
13928 }
13929
13930 case ID_ZOOMOUT: {
13931 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13932 break;
13933 }
13934
13935 case ID_STKUP:
13936 DoCanvasStackDelta(1);
13937 DoCanvasUpdate();
13938 break;
13939
13940 case ID_STKDN:
13941 DoCanvasStackDelta(-1);
13942 DoCanvasUpdate();
13943 break;
13944
13945 case ID_FOLLOW: {
13946 TogglebFollow();
13947 break;
13948 }
13949
13950 case ID_CURRENT: {
13951 ShowCurrents(!GetbShowCurrent());
13952 ReloadVP();
13953 Refresh(false);
13954 break;
13955 }
13956
13957 case ID_TIDE: {
13958 ShowTides(!GetbShowTide());
13959 ReloadVP();
13960 Refresh(false);
13961 break;
13962 }
13963
13964 case ID_ROUTE: {
13965 if (0 == m_routeState) {
13966 StartRoute();
13967 } else {
13968 FinishRoute();
13969 }
13970
13971#ifdef __ANDROID__
13972 androidSetRouteAnnunciator(m_routeState == 1);
13973#endif
13974 break;
13975 }
13976
13977 case ID_AIS: {
13978 SetAISCanvasDisplayStyle(-1);
13979 break;
13980 }
13981
13982 default:
13983 break;
13984 }
13985
13986 // And then let gFrame handle the rest....
13987 event.Skip();
13988}
13989
13990void ChartCanvas::SetShowAIS(bool show) {
13991 m_bShowAIS = show;
13992 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13993 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13994}
13995
13996void ChartCanvas::SetAttenAIS(bool show) {
13997 m_bShowAISScaled = show;
13998 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13999 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14000}
14001
14002void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14003 // make some arrays to hold the dfferences between cycle steps
14004 // show all, scaled, hide all
14005 bool bShowAIS_Array[3] = {true, true, false};
14006 bool bShowScaled_Array[3] = {false, true, true};
14007 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14008 _("Attenuate less critical AIS targets"),
14009 _("Hide AIS Targets")};
14010 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14011 int ArraySize = 3;
14012 int AIS_Toolbar_Switch = 0;
14013 if (StyleIndx == -1) { // -1 means coming from toolbar button
14014 // find current state of switch
14015 for (int i = 1; i < ArraySize; i++) {
14016 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14017 (bShowScaled_Array[i] == m_bShowAISScaled))
14018 AIS_Toolbar_Switch = i;
14019 }
14020 AIS_Toolbar_Switch++; // we did click so continu with next item
14021 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14022 AIS_Toolbar_Switch++;
14023
14024 } else { // coming from menu bar.
14025 AIS_Toolbar_Switch = StyleIndx;
14026 }
14027 // make sure we are not above array
14028 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14029
14030 int AIS_Toolbar_Switch_Next =
14031 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14032 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14033 AIS_Toolbar_Switch_Next++;
14034 if (AIS_Toolbar_Switch_Next >= ArraySize)
14035 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14036
14037 // Set found values to global and member variables
14038 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14039 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14040}
14041
14042void ChartCanvas::TouchAISToolActive() {}
14043
14044void ChartCanvas::UpdateAISTBTool() {}
14045
14046//---------------------------------------------------------------------------------
14047//
14048// Compass/GPS status icon support
14049//
14050//---------------------------------------------------------------------------------
14051
14052void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14053 // Look for change in overlap or positions
14054 bool b_update = false;
14055 int cc1_edge_comp = 2;
14056 wxRect rect = m_Compass->GetRect();
14057 wxSize parent_size = GetSize();
14058
14059 parent_size *= m_displayScale;
14060
14061 // check to see if it would overlap if it was in its home position (upper
14062 // right)
14063 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14064 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14065 wxRect compass_rect(compass_pt, rect.GetSize());
14066
14067 m_Compass->Move(compass_pt);
14068
14069 if (m_Compass && m_Compass->IsShown())
14070 m_Compass->UpdateStatus(b_force_new | b_update);
14071
14072 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14073 scaler = wxMax(scaler, 1.0);
14074 wxPoint note_point = wxPoint(
14075 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14076 if (m_notification_button) {
14077 m_notification_button->Move(note_point);
14078 m_notification_button->UpdateStatus();
14079 }
14080
14081 if (b_force_new | b_update) Refresh();
14082}
14083
14084void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14085 ChartTypeEnum New_Type,
14086 ChartFamilyEnum New_Family) {
14087 if (!GetpCurrentStack()) return;
14088 if (!ChartData) return;
14089
14090 if (index < GetpCurrentStack()->nEntry) {
14091 // Open the new chart
14092 ChartBase *pTentative_Chart;
14093 pTentative_Chart = ChartData->OpenStackChartConditional(
14094 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14095
14096 if (pTentative_Chart) {
14097 if (m_singleChart) m_singleChart->Deactivate();
14098
14099 m_singleChart = pTentative_Chart;
14100 m_singleChart->Activate();
14101
14102 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14103 GetpCurrentStack(), m_singleChart->GetFullPath());
14104 }
14105
14106 // Setup the view
14107 double zLat, zLon;
14108 if (m_bFollow) {
14109 zLat = gLat;
14110 zLon = gLon;
14111 } else {
14112 zLat = m_vLat;
14113 zLon = m_vLon;
14114 }
14115
14116 double best_scale_ppm = GetBestVPScale(m_singleChart);
14117 double rotation = GetVPRotation();
14118 double oldskew = GetVPSkew();
14119 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14120
14121 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14122 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14123 if (fabs(newskew) > 0.0001) rotation = newskew;
14124 }
14125
14126 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14127
14128 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14129 }
14130
14131 // refresh Piano
14132 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14133 if (idx < 0) return;
14134
14135 std::vector<int> piano_active_chart_index_array;
14136 piano_active_chart_index_array.push_back(
14137 GetpCurrentStack()->GetCurrentEntrydbIndex());
14138 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14139}
14140
14141void ChartCanvas::SelectdbChart(int dbindex) {
14142 if (!GetpCurrentStack()) return;
14143 if (!ChartData) return;
14144
14145 if (dbindex >= 0) {
14146 // Open the new chart
14147 ChartBase *pTentative_Chart;
14148 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14149
14150 if (pTentative_Chart) {
14151 if (m_singleChart) m_singleChart->Deactivate();
14152
14153 m_singleChart = pTentative_Chart;
14154 m_singleChart->Activate();
14155
14156 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14157 GetpCurrentStack(), m_singleChart->GetFullPath());
14158 }
14159
14160 // Setup the view
14161 double zLat, zLon;
14162 if (m_bFollow) {
14163 zLat = gLat;
14164 zLon = gLon;
14165 } else {
14166 zLat = m_vLat;
14167 zLon = m_vLon;
14168 }
14169
14170 double best_scale_ppm = GetBestVPScale(m_singleChart);
14171
14172 if (m_singleChart)
14173 SetViewPoint(zLat, zLon, best_scale_ppm,
14174 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14175
14176 // SetChartUpdatePeriod( );
14177
14178 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14179 }
14180
14181 // TODO refresh_Piano();
14182}
14183
14184void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14185 double target_scale = GetVP().view_scale_ppm;
14186
14187 if (!GetQuiltMode()) {
14188 if (GetpCurrentStack()) {
14189 int stack_index = -1;
14190 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14191 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14192 if (check_dbIndex < 0) continue;
14193 const ChartTableEntry &cte =
14194 ChartData->GetChartTableEntry(check_dbIndex);
14195 if (type == cte.GetChartType()) {
14196 stack_index = i;
14197 break;
14198 } else if (family == cte.GetChartFamily()) {
14199 stack_index = i;
14200 break;
14201 }
14202 }
14203
14204 if (stack_index >= 0) {
14205 SelectChartFromStack(stack_index);
14206 }
14207 }
14208 } else {
14209 int sel_dbIndex = -1;
14210 std::vector<int> piano_chart_index_array =
14211 GetQuiltExtendedStackdbIndexArray();
14212 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14213 int check_dbIndex = piano_chart_index_array[i];
14214 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14215 if (type == cte.GetChartType()) {
14216 if (IsChartQuiltableRef(check_dbIndex)) {
14217 sel_dbIndex = check_dbIndex;
14218 break;
14219 }
14220 } else if (family == cte.GetChartFamily()) {
14221 if (IsChartQuiltableRef(check_dbIndex)) {
14222 sel_dbIndex = check_dbIndex;
14223 break;
14224 }
14225 }
14226 }
14227
14228 if (sel_dbIndex >= 0) {
14229 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14230 // Re-qualify the quilt reference chart selection
14231 AdjustQuiltRefChart();
14232 }
14233
14234 // Now reset the scale to the target...
14235 SetVPScale(target_scale);
14236 }
14237
14238 SetQuiltChartHiLiteIndex(-1);
14239
14240 ReloadVP();
14241}
14242
14243bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14244 return std::find(m_tile_yesshow_index_array.begin(),
14245 m_tile_yesshow_index_array.end(),
14246 index) != m_tile_yesshow_index_array.end();
14247}
14248
14249bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14250 return std::find(m_tile_noshow_index_array.begin(),
14251 m_tile_noshow_index_array.end(),
14252 index) != m_tile_noshow_index_array.end();
14253}
14254
14255void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14256 if (std::find(m_tile_noshow_index_array.begin(),
14257 m_tile_noshow_index_array.end(),
14258 index) == m_tile_noshow_index_array.end()) {
14259 m_tile_noshow_index_array.push_back(index);
14260 }
14261}
14262
14263//-------------------------------------------------------------------------------------------------------
14264//
14265// Piano support
14266//
14267//-------------------------------------------------------------------------------------------------------
14268
14269void ChartCanvas::HandlePianoClick(
14270 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14271 if (g_options && g_options->IsShown())
14272 return; // Piano might be invalid due to chartset updates.
14273 if (!m_pCurrentStack) return;
14274 if (!ChartData) return;
14275
14276 // stop movement or on slow computer we may get something like :
14277 // zoom out with the wheel (timer is set)
14278 // quickly click and display a chart, which may zoom in
14279 // but the delayed timer fires first and it zooms out again!
14280 StopMovement();
14281
14282 // When switching by piano key click, we may appoint the new target chart to
14283 // be any chart in the composite array.
14284 // As an improvement to UX, find the chart that is "closest" to the current
14285 // vp,
14286 // and select that chart. This will cause a jump to the centroid of that
14287 // chart
14288
14289 double distance = 25000; // RTW
14290 int closest_index = -1;
14291 for (int chart_index : selected_dbIndex_array) {
14292 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14293 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14294 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14295
14296 // measure distance as Manhattan style
14297 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14298 if (test_distance < distance) {
14299 distance = test_distance;
14300 closest_index = chart_index;
14301 }
14302 }
14303
14304 int selected_dbIndex = selected_dbIndex_array[0];
14305 if (closest_index >= 0) selected_dbIndex = closest_index;
14306
14307 if (!GetQuiltMode()) {
14308 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14309 if (IsChartQuiltableRef(selected_dbIndex)) {
14310 ToggleCanvasQuiltMode();
14311 SelectQuiltRefdbChart(selected_dbIndex);
14312 m_bpersistent_quilt = false;
14313 } else {
14314 SelectChartFromStack(selected_index);
14315 }
14316 } else {
14317 SelectChartFromStack(selected_index);
14318 g_sticky_chart = selected_dbIndex;
14319 }
14320
14321 if (m_singleChart)
14322 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14323 } else {
14324 // Handle MBTiles overlays first
14325 // Left click simply toggles the noshow array index entry
14326 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14327 bool bfound = false;
14328 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14329 if (m_tile_noshow_index_array[i] ==
14330 selected_dbIndex) { // chart is in the noshow list
14331 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14332 i); // erase it
14333 bfound = true;
14334 break;
14335 }
14336 }
14337 if (!bfound) {
14338 m_tile_noshow_index_array.push_back(selected_dbIndex);
14339 }
14340
14341 // If not already present, add this tileset to the "yes_show" array.
14342 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14343 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14344 }
14345
14346 else {
14347 if (IsChartQuiltableRef(selected_dbIndex)) {
14348 // if( ChartData ) ChartData->PurgeCache();
14349
14350 // If the chart is a vector chart, and of very large scale,
14351 // then we had better set the new scale directly to avoid excessive
14352 // underzoom on, eg, Inland ENCs
14353 bool set_scale = false;
14354 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14355 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14356 set_scale = true;
14357 }
14358 }
14359
14360 if (!set_scale) {
14361 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14362 } else {
14363 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14364
14365 // Adjust scale so that the selected chart is underzoomed/overzoomed
14366 // by a controlled amount
14367 ChartBase *pc =
14368 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14369 if (pc) {
14370 double proposed_scale_onscreen =
14372
14373 if (g_bPreserveScaleOnX) {
14374 proposed_scale_onscreen =
14375 wxMin(proposed_scale_onscreen,
14376 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14377 GetCanvasWidth()));
14378 } else {
14379 proposed_scale_onscreen =
14380 wxMin(proposed_scale_onscreen,
14381 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14382 GetCanvasWidth()));
14383
14384 proposed_scale_onscreen =
14385 wxMax(proposed_scale_onscreen,
14386 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14388 }
14389
14390 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14391 }
14392 }
14393 } else {
14394 ToggleCanvasQuiltMode();
14395 SelectdbChart(selected_dbIndex);
14396 m_bpersistent_quilt = true;
14397 }
14398 }
14399 }
14400
14401 SetQuiltChartHiLiteIndex(-1);
14402 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14403 // (checkmarks etc)
14404 HideChartInfoWindow();
14405 DoCanvasUpdate();
14406 ReloadVP(); // Pick up the new selections
14407}
14408
14409void ChartCanvas::HandlePianoRClick(
14410 int x, int y, int selected_index,
14411 const std::vector<int> &selected_dbIndex_array) {
14412 if (g_options && g_options->IsShown())
14413 return; // Piano might be invalid due to chartset updates.
14414 if (!GetpCurrentStack()) return;
14415
14416 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14417 UpdateCanvasControlBar();
14418
14419 SetQuiltChartHiLiteIndex(-1);
14420}
14421
14422void ChartCanvas::HandlePianoRollover(
14423 int selected_index, const std::vector<int> &selected_dbIndex_array,
14424 int n_charts, int scale) {
14425 if (g_options && g_options->IsShown())
14426 return; // Piano might be invalid due to chartset updates.
14427 if (!GetpCurrentStack()) return;
14428 if (!ChartData) return;
14429
14430 if (ChartData->IsBusy()) return;
14431
14432 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14433
14434 if (!GetQuiltMode()) {
14435 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14436 } else {
14437 // Select the correct vector
14438 std::vector<int> piano_chart_index_array;
14439 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14440 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14441 if ((GetpCurrentStack()->nEntry > 1) ||
14442 (piano_chart_index_array.size() >= 1)) {
14443 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14444
14445 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14446 ReloadVP(false); // no VP adjustment allowed
14447 } else if (GetpCurrentStack()->nEntry == 1) {
14448 const ChartTableEntry &cte =
14449 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14450 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14451 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14452 ReloadVP(false);
14453 } else if ((-1 == selected_index) &&
14454 (0 == selected_dbIndex_array.size())) {
14455 ShowChartInfoWindow(key_location.x, -1);
14456 }
14457 }
14458 } else {
14459 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14460
14461 if ((GetpCurrentStack()->nEntry > 1) ||
14462 (piano_chart_index_array.size() >= 1)) {
14463 if (n_charts > 1)
14464 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14465 selected_dbIndex_array);
14466 else if (n_charts == 1)
14467 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14468
14469 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14470 ReloadVP(false); // no VP adjustment allowed
14471 }
14472 }
14473 }
14474}
14475
14476void ChartCanvas::ClearPianoRollover() {
14477 ClearQuiltChartHiLiteIndexArray();
14478 ShowChartInfoWindow(0, -1);
14479 std::vector<int> vec;
14480 ShowCompositeInfoWindow(0, 0, 0, vec);
14481 ReloadVP(false);
14482}
14483
14484void ChartCanvas::UpdateCanvasControlBar() {
14485 if (m_pianoFrozen) return;
14486
14487 if (!GetpCurrentStack()) return;
14488 if (!ChartData) return;
14489 if (!g_bShowChartBar) return;
14490
14491 int sel_type = -1;
14492 int sel_family = -1;
14493
14494 std::vector<int> piano_chart_index_array;
14495 std::vector<int> empty_piano_chart_index_array;
14496
14497 wxString old_hash = m_Piano->GetStoredHash();
14498
14499 if (GetQuiltMode()) {
14500 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14501 GetQuiltFullScreendbIndexArray());
14502
14503 std::vector<int> piano_active_chart_index_array =
14504 GetQuiltCandidatedbIndexArray();
14505 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14506
14507 std::vector<int> piano_eclipsed_chart_index_array =
14508 GetQuiltEclipsedStackdbIndexArray();
14509 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14510
14511 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14512 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14513
14514 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14515 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14516 } else {
14517 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14518 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14519 // TODO refresh_Piano();
14520
14521 if (m_singleChart) {
14522 sel_type = m_singleChart->GetChartType();
14523 sel_family = m_singleChart->GetChartFamily();
14524 }
14525 }
14526
14527 // Set up the TMerc and Skew arrays
14528 std::vector<int> piano_skew_chart_index_array;
14529 std::vector<int> piano_tmerc_chart_index_array;
14530 std::vector<int> piano_poly_chart_index_array;
14531
14532 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14533 const ChartTableEntry &ctei =
14534 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14535 double skew_norm = ctei.GetChartSkew();
14536 if (skew_norm > 180.) skew_norm -= 360.;
14537
14538 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14539 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14540
14541 // Polyconic skewed charts should show as skewed
14542 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14543 if (fabs(skew_norm) > 1.)
14544 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14545 else
14546 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14547 } else if (fabs(skew_norm) > 1.)
14548 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14549 }
14550 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14551 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14552 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14553
14554 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14555 if (new_hash != old_hash) {
14556 m_Piano->FormatKeys();
14557 HideChartInfoWindow();
14558 m_Piano->ResetRollover();
14559 SetQuiltChartHiLiteIndex(-1);
14560 m_brepaint_piano = true;
14561 }
14562
14563 // Create a bitmask int that describes what Family/Type of charts are shown in
14564 // the bar, and notify the platform.
14565 int mask = 0;
14566 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14567 const ChartTableEntry &ctei =
14568 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14569 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14570 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14571 if (e == CHART_FAMILY_RASTER) mask |= 1;
14572 if (e == CHART_FAMILY_VECTOR) {
14573 if (t == CHART_TYPE_CM93COMP)
14574 mask |= 4;
14575 else
14576 mask |= 2;
14577 }
14578 }
14579
14580 wxString s_indicated;
14581 if (sel_type == CHART_TYPE_CM93COMP)
14582 s_indicated = "cm93";
14583 else {
14584 if (sel_family == CHART_FAMILY_RASTER)
14585 s_indicated = "raster";
14586 else if (sel_family == CHART_FAMILY_VECTOR)
14587 s_indicated = "vector";
14588 }
14589
14590 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14591}
14592
14593void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14594
14595void ChartCanvas::PianoPopupMenu(
14596 int x, int y, int selected_index,
14597 const std::vector<int> &selected_dbIndex_array) {
14598 if (!GetpCurrentStack()) return;
14599
14600 // No context menu if quilting is disabled
14601 if (!GetQuiltMode()) return;
14602
14603 m_piano_ctx_menu = new wxMenu();
14604
14605 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14606 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14607 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14608 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14609 } else {
14610 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14611 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14612 // wxEVT_COMMAND_MENU_SELECTED,
14613 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14614
14615 menu_selected_dbIndex = selected_dbIndex_array[0];
14616 menu_selected_index = selected_index;
14617
14618 // Search the no-show array
14619 bool b_is_in_noshow = false;
14620 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14621 if (m_quilt_noshow_index_array[i] ==
14622 menu_selected_dbIndex) // chart is in the noshow list
14623 {
14624 b_is_in_noshow = true;
14625 break;
14626 }
14627 }
14628
14629 if (b_is_in_noshow) {
14630 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14631 _("Show This Chart"));
14632 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14633 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14634 } else if (GetpCurrentStack()->nEntry > 1) {
14635 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14636 _("Hide This Chart"));
14637 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14638 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14639 }
14640 }
14641
14642 wxPoint pos = wxPoint(x, y - 30);
14643
14644 // Invoke the drop-down menu
14645 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14646 PopupMenu(m_piano_ctx_menu, pos);
14647
14648 delete m_piano_ctx_menu;
14649 m_piano_ctx_menu = NULL;
14650
14651 HideChartInfoWindow();
14652 m_Piano->ResetRollover();
14653
14654 SetQuiltChartHiLiteIndex(-1);
14655 ClearQuiltChartHiLiteIndexArray();
14656
14657 ReloadVP();
14658}
14659
14660void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14661 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14662 if (m_quilt_noshow_index_array[i] ==
14663 menu_selected_dbIndex) // chart is in the noshow list
14664 {
14665 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14666 break;
14667 }
14668 }
14669}
14670
14671void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14672 if (!GetpCurrentStack()) return;
14673 if (!ChartData) return;
14674
14675 RemoveChartFromQuilt(menu_selected_dbIndex);
14676
14677 // It could happen that the chart being disabled is the reference
14678 // chart....
14679 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14680 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14681
14682 int i = menu_selected_index + 1; // select next smaller scale chart
14683 bool b_success = false;
14684 while (i < GetpCurrentStack()->nEntry - 1) {
14685 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14686 if (type == ChartData->GetDBChartType(dbIndex)) {
14687 SelectQuiltRefChart(i);
14688 b_success = true;
14689 break;
14690 }
14691 i++;
14692 }
14693
14694 // If that did not work, try to select the next larger scale compatible
14695 // chart
14696 if (!b_success) {
14697 i = menu_selected_index - 1;
14698 while (i > 0) {
14699 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14700 if (type == ChartData->GetDBChartType(dbIndex)) {
14701 SelectQuiltRefChart(i);
14702 b_success = true;
14703 break;
14704 }
14705 i--;
14706 }
14707 }
14708 }
14709}
14710
14711void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14712 // Remove the item from the list (if it appears) to avoid multiple addition
14713 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14714 if (m_quilt_noshow_index_array[i] ==
14715 dbIndex) // chart is already in the noshow list
14716 {
14717 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14718 break;
14719 }
14720 }
14721
14722 m_quilt_noshow_index_array.push_back(dbIndex);
14723}
14724
14725bool ChartCanvas::UpdateS52State() {
14726 bool retval = false;
14727
14728 if (ps52plib) {
14729 ps52plib->SetShowS57Text(m_encShowText);
14730 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14731 ps52plib->m_bShowSoundg = m_encShowDepth;
14732 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14733 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14734
14735 // Lights
14736 if (!m_encShowLights) // On, going off
14737 ps52plib->AddObjNoshow("LIGHTS");
14738 else // Off, going on
14739 ps52plib->RemoveObjNoshow("LIGHTS");
14740 ps52plib->SetLightsOff(!m_encShowLights);
14741 ps52plib->m_bExtendLightSectors = true;
14742
14743 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14744 ps52plib->SetAnchorOn(m_encShowAnchor);
14745 ps52plib->SetQualityOfData(m_encShowDataQual);
14746 }
14747
14748 return retval;
14749}
14750
14751void ChartCanvas::SetShowENCDataQual(bool show) {
14752 m_encShowDataQual = show;
14753 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14754 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14755
14756 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14757}
14758
14759void ChartCanvas::SetShowENCText(bool show) {
14760 m_encShowText = show;
14761 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14762 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14763
14764 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14765}
14766
14767void ChartCanvas::SetENCDisplayCategory(int category) {
14768 m_encDisplayCategory = category;
14769 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14770}
14771
14772void ChartCanvas::SetShowENCDepth(bool show) {
14773 m_encShowDepth = show;
14774 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14775 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14776
14777 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14778}
14779
14780void ChartCanvas::SetShowENCLightDesc(bool show) {
14781 m_encShowLightDesc = show;
14782 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14783 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14784
14785 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14786}
14787
14788void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14789 m_encShowBuoyLabels = show;
14790 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14791}
14792
14793void ChartCanvas::SetShowENCLights(bool show) {
14794 m_encShowLights = show;
14795 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14796 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14797
14798 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14799}
14800
14801void ChartCanvas::SetShowENCAnchor(bool show) {
14802 m_encShowAnchor = show;
14803 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14804 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14805
14806 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14807}
14808
14809wxRect ChartCanvas::GetMUIBarRect() {
14810 wxRect rv;
14811 if (m_muiBar) {
14812 rv = m_muiBar->GetRect();
14813 }
14814
14815 return rv;
14816}
14817
14818void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14819 if (!GetAlertString().IsEmpty()) {
14820 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14821 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14822
14823 dc.SetFont(*pfont);
14824 dc.SetPen(*wxTRANSPARENT_PEN);
14825
14826 dc.SetBrush(wxColour(243, 229, 47));
14827 int w, h;
14828 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14829 h += 2;
14830 // int yp = vp.pix_height - 20 - h;
14831
14832 wxRect sbr = GetScaleBarRect();
14833 int xp = sbr.x + sbr.width + 10;
14834 int yp = (sbr.y + sbr.height) - h;
14835
14836 int wdraw = w + 10;
14837 dc.DrawRectangle(xp, yp, wdraw, h);
14838 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14839 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14840 }
14841}
14842
14843//--------------------------------------------------------------------------------------------------------
14844// Screen Brightness Control Support Routines
14845//
14846//--------------------------------------------------------------------------------------------------------
14847
14848#ifdef __UNIX__
14849#define BRIGHT_XCALIB
14850#define __OPCPN_USEICC__
14851#endif
14852
14853#ifdef __OPCPN_USEICC__
14854int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14855 double co_green, double co_blue);
14856
14857wxString temp_file_name;
14858#endif
14859
14860#if 0
14861class ocpnCurtain: public wxDialog
14862{
14863 DECLARE_CLASS( ocpnCurtain )
14864 DECLARE_EVENT_TABLE()
14865
14866public:
14867 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14868 ~ocpnCurtain( );
14869 bool ProcessEvent(wxEvent& event);
14870
14871};
14872
14873IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14874
14875BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14876END_EVENT_TABLE()
14877
14878ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14879{
14880 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14881}
14882
14883ocpnCurtain::~ocpnCurtain()
14884{
14885}
14886
14887bool ocpnCurtain::ProcessEvent(wxEvent& event)
14888{
14889 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14890 return GetParent()->GetEventHandler()->ProcessEvent(event);
14891}
14892#endif
14893
14894#ifdef _WIN32
14895#include <windows.h>
14896
14897HMODULE hGDI32DLL;
14898typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14899typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14900SetDeviceGammaRamp_ptr_type
14901 g_pSetDeviceGammaRamp; // the API entry points in the dll
14902GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14903
14904WORD *g_pSavedGammaMap;
14905
14906#endif
14907
14908int InitScreenBrightness() {
14909#ifdef _WIN32
14910#ifdef ocpnUSE_GL
14911 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14912 HDC hDC;
14913 BOOL bbr;
14914
14915 if (NULL == hGDI32DLL) {
14916 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14917
14918 if (NULL != hGDI32DLL) {
14919 // Get the entry points of the required functions
14920 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14921 hGDI32DLL, "SetDeviceGammaRamp");
14922 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14923 hGDI32DLL, "GetDeviceGammaRamp");
14924
14925 // If the functions are not found, unload the DLL and return false
14926 if ((NULL == g_pSetDeviceGammaRamp) ||
14927 (NULL == g_pGetDeviceGammaRamp)) {
14928 FreeLibrary(hGDI32DLL);
14929 hGDI32DLL = NULL;
14930 return 0;
14931 }
14932 }
14933 }
14934
14935 // Interface is ready, so....
14936 // Get some storage
14937 if (!g_pSavedGammaMap) {
14938 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14939
14940 hDC = GetDC(NULL); // Get the full screen DC
14941 bbr = g_pGetDeviceGammaRamp(
14942 hDC, g_pSavedGammaMap); // Get the existing ramp table
14943 ReleaseDC(NULL, hDC); // Release the DC
14944 }
14945
14946 // On Windows hosts, try to adjust the registry to allow full range
14947 // setting of Gamma table This is an undocumented Windows hack.....
14948 wxRegKey *pRegKey = new wxRegKey(
14949 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
14950 "NT\\CurrentVersion\\ICM");
14951 if (!pRegKey->Exists()) pRegKey->Create();
14952 pRegKey->SetValue("GdiIcmGammaRange", 256);
14953
14954 g_brightness_init = true;
14955 return 1;
14956 }
14957#endif
14958
14959 {
14960 if (NULL == g_pcurtain) {
14961 if (gFrame->CanSetTransparent()) {
14962 // Build the curtain window
14963 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, "",
14964 wxPoint(0, 0), ::wxGetDisplaySize(),
14965 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14966 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14967
14968 // g_pcurtain = new ocpnCurtain(gFrame,
14969 // wxPoint(0,0),::wxGetDisplaySize(),
14970 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14971 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14972
14973 g_pcurtain->Hide();
14974
14975 HWND hWnd = GetHwndOf(g_pcurtain);
14976 SetWindowLong(hWnd, GWL_EXSTYLE,
14977 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14978 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14979 g_pcurtain->SetTransparent(0);
14980
14981 g_pcurtain->Maximize();
14982 g_pcurtain->Show();
14983
14984 // All of this is obtuse, but necessary for Windows...
14985 g_pcurtain->Enable();
14986 g_pcurtain->Disable();
14987
14988 gFrame->Disable();
14989 gFrame->Enable();
14990 // SetFocus();
14991 }
14992 }
14993 g_brightness_init = true;
14994
14995 return 1;
14996 }
14997#else
14998 // Look for "xcalib" application
14999 wxString cmd("xcalib -version");
15000
15001 wxArrayString output;
15002 long r = wxExecute(cmd, output);
15003 if (0 != r)
15004 wxLogMessage(
15005 " External application \"xcalib\" not found. Screen brightness "
15006 "not changed.");
15007
15008 g_brightness_init = true;
15009 return 0;
15010#endif
15011}
15012
15013int RestoreScreenBrightness() {
15014#ifdef _WIN32
15015
15016 if (g_pSavedGammaMap) {
15017 HDC hDC = GetDC(NULL); // Get the full screen DC
15018 g_pSetDeviceGammaRamp(hDC,
15019 g_pSavedGammaMap); // Restore the saved ramp table
15020 ReleaseDC(NULL, hDC); // Release the DC
15021
15022 free(g_pSavedGammaMap);
15023 g_pSavedGammaMap = NULL;
15024 }
15025
15026 if (g_pcurtain) {
15027 g_pcurtain->Close();
15028 g_pcurtain->Destroy();
15029 g_pcurtain = NULL;
15030 }
15031
15032 g_brightness_init = false;
15033 return 1;
15034
15035#endif
15036
15037#ifdef BRIGHT_XCALIB
15038 if (g_brightness_init) {
15039 wxString cmd;
15040 cmd = "xcalib -clear";
15041 wxExecute(cmd, wxEXEC_ASYNC);
15042 g_brightness_init = false;
15043 }
15044
15045 return 1;
15046#endif
15047
15048 return 0;
15049}
15050
15051// Set brightness. [0..100]
15052int SetScreenBrightness(int brightness) {
15053#ifdef _WIN32
15054
15055 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15056 // some (most modern?) versions of gdi32.dll Load the required library dll,
15057 // if not already in place
15058#ifdef ocpnUSE_GL
15059 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
15060 if (g_pcurtain) {
15061 g_pcurtain->Close();
15062 g_pcurtain->Destroy();
15063 g_pcurtain = NULL;
15064 }
15065
15066 InitScreenBrightness();
15067
15068 if (NULL == hGDI32DLL) {
15069 // Unicode stuff.....
15070 wchar_t wdll_name[80];
15071 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15072 LPCWSTR cstr = wdll_name;
15073
15074 hGDI32DLL = LoadLibrary(cstr);
15075
15076 if (NULL != hGDI32DLL) {
15077 // Get the entry points of the required functions
15078 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15079 hGDI32DLL, "SetDeviceGammaRamp");
15080 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15081 hGDI32DLL, "GetDeviceGammaRamp");
15082
15083 // If the functions are not found, unload the DLL and return false
15084 if ((NULL == g_pSetDeviceGammaRamp) ||
15085 (NULL == g_pGetDeviceGammaRamp)) {
15086 FreeLibrary(hGDI32DLL);
15087 hGDI32DLL = NULL;
15088 return 0;
15089 }
15090 }
15091 }
15092
15093 HDC hDC = GetDC(NULL); // Get the full screen DC
15094
15095 /*
15096 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15097 if (cmcap != CM_GAMMA_RAMP)
15098 {
15099 wxLogMessage(" Video hardware does not support brightness control by
15100 gamma ramp adjustment."); return false;
15101 }
15102 */
15103
15104 int increment = brightness * 256 / 100;
15105
15106 // Build the Gamma Ramp table
15107 WORD GammaTable[3][256];
15108
15109 int table_val = 0;
15110 for (int i = 0; i < 256; i++) {
15111 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15112 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15113 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15114
15115 table_val += increment;
15116
15117 if (table_val > 65535) table_val = 65535;
15118 }
15119
15120 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15121 ReleaseDC(NULL, hDC); // Release the DC
15122
15123 return 1;
15124 }
15125#endif
15126
15127 {
15128 if (g_pSavedGammaMap) {
15129 HDC hDC = GetDC(NULL); // Get the full screen DC
15130 g_pSetDeviceGammaRamp(hDC,
15131 g_pSavedGammaMap); // Restore the saved ramp table
15132 ReleaseDC(NULL, hDC); // Release the DC
15133 }
15134
15135 if (brightness < 100) {
15136 if (NULL == g_pcurtain) InitScreenBrightness();
15137
15138 if (g_pcurtain) {
15139 int sbrite = wxMax(1, brightness);
15140 sbrite = wxMin(100, sbrite);
15141
15142 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15143 }
15144 } else {
15145 if (g_pcurtain) {
15146 g_pcurtain->Close();
15147 g_pcurtain->Destroy();
15148 g_pcurtain = NULL;
15149 }
15150 }
15151
15152 return 1;
15153 }
15154
15155#endif
15156
15157#ifdef BRIGHT_XCALIB
15158
15159 if (!g_brightness_init) {
15160 last_brightness = 100;
15161 g_brightness_init = true;
15162 temp_file_name = wxFileName::CreateTempFileName("");
15163 InitScreenBrightness();
15164 }
15165
15166#ifdef __OPCPN_USEICC__
15167 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15168 // desired, and then activate this temporary profile using xcalib <filename>
15169 if (!CreateSimpleICCProfileFile(
15170 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15171 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15172 wxString cmd("xcalib ");
15173 cmd += temp_file_name;
15174
15175 wxExecute(cmd, wxEXEC_ASYNC);
15176 }
15177
15178#else
15179 // Or, use "xcalib -co" to set overall contrast value
15180 // This is not as nice, since the -co parameter wants to be a fraction of
15181 // the current contrast, and values greater than 100 are not allowed. As a
15182 // result, increases of contrast must do a "-clear" step first, which
15183 // produces objectionable flashing.
15184 if (brightness > last_brightness) {
15185 wxString cmd;
15186 cmd = "xcalib -clear";
15187 wxExecute(cmd, wxEXEC_ASYNC);
15188
15189 ::wxMilliSleep(10);
15190
15191 int brite_adj = wxMax(1, brightness);
15192 cmd.Printf("xcalib -co %2d -a", brite_adj);
15193 wxExecute(cmd, wxEXEC_ASYNC);
15194 } else {
15195 int brite_adj = wxMax(1, brightness);
15196 int factor = (brite_adj * 100) / last_brightness;
15197 factor = wxMax(1, factor);
15198 wxString cmd;
15199 cmd.Printf("xcalib -co %2d -a", factor);
15200 wxExecute(cmd, wxEXEC_ASYNC);
15201 }
15202
15203#endif
15204
15205 last_brightness = brightness;
15206
15207#endif
15208
15209 return 0;
15210}
15211
15212#ifdef __OPCPN_USEICC__
15213
15214#define MLUT_TAG 0x6d4c5554L
15215#define VCGT_TAG 0x76636774L
15216
15217int GetIntEndian(unsigned char *s) {
15218 int ret;
15219 unsigned char *p;
15220 int i;
15221
15222 p = (unsigned char *)&ret;
15223
15224 if (1)
15225 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15226 else
15227 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15228
15229 return ret;
15230}
15231
15232unsigned short GetShortEndian(unsigned char *s) {
15233 unsigned short ret;
15234 unsigned char *p;
15235 int i;
15236
15237 p = (unsigned char *)&ret;
15238
15239 if (1)
15240 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15241 else
15242 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15243
15244 return ret;
15245}
15246
15247// Create a very simple Gamma correction file readable by xcalib
15248int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15249 double co_green, double co_blue) {
15250 FILE *fp;
15251
15252 if (file_name) {
15253 fp = fopen(file_name, "wb");
15254 if (!fp) return -1; /* file can not be created */
15255 } else
15256 return -1; /* filename char pointer not valid */
15257
15258 // Write header
15259 char header[128];
15260 for (int i = 0; i < 128; i++) header[i] = 0;
15261
15262 fwrite(header, 128, 1, fp);
15263
15264 // Num tags
15265 int numTags0 = 1;
15266 int numTags = GetIntEndian((unsigned char *)&numTags0);
15267 fwrite(&numTags, 1, 4, fp);
15268
15269 int tagName0 = VCGT_TAG;
15270 int tagName = GetIntEndian((unsigned char *)&tagName0);
15271 fwrite(&tagName, 1, 4, fp);
15272
15273 int tagOffset0 = 128 + 4 * sizeof(int);
15274 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15275 fwrite(&tagOffset, 1, 4, fp);
15276
15277 int tagSize0 = 1;
15278 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15279 fwrite(&tagSize, 1, 4, fp);
15280
15281 fwrite(&tagName, 1, 4, fp); // another copy of tag
15282
15283 fwrite(&tagName, 1, 4, fp); // dummy
15284
15285 // Table type
15286
15287 /* VideoCardGammaTable (The simplest type) */
15288 int gammatype0 = 0;
15289 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15290 fwrite(&gammatype, 1, 4, fp);
15291
15292 int numChannels0 = 3;
15293 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15294 fwrite(&numChannels, 1, 2, fp);
15295
15296 int numEntries0 = 256;
15297 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15298 fwrite(&numEntries, 1, 2, fp);
15299
15300 int entrySize0 = 1;
15301 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15302 fwrite(&entrySize, 1, 2, fp);
15303
15304 unsigned char ramp[256];
15305
15306 // Red ramp
15307 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15308 fwrite(ramp, 256, 1, fp);
15309
15310 // Green ramp
15311 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15312 fwrite(ramp, 256, 1, fp);
15313
15314 // Blue ramp
15315 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15316 fwrite(ramp, 256, 1, fp);
15317
15318 fclose(fp);
15319
15320 return 0;
15321}
15322#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:726
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:13685
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:4519
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11729
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4515
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3613
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13644
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:4465
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:7950
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7759
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5047
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:4596
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5327
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:4540
bool IsTideDialogOpen() const
Definition chcanv.cpp:13683
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:4602
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13640
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4460
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5346
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10143
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:1718
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
wxString m_MarkDescription
Description text for the waypoint.
LLBBox m_wpBBox
Bounding box for the waypoint.
bool m_bRPIsBeingEdited
Flag indicating if this waypoint is currently being edited.
wxRect CurrentRect_in_DC
Current rectangle occupied by the waypoint in the display.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bIsActive
Flag indicating if this waypoint is active for navigation.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
double m_seg_len
Length of the leg from previous waypoint to this waypoint in nautical miles.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
bool m_bBlink
Flag indicating if the waypoint should blink when displayed.
bool m_bPtIsSelected
Flag indicating if this waypoint is currently selected.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
Represents a navigational route in the navigation system.
Definition route.h:99
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:203
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:336
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:237
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:208
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:288
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:247
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:273
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:278
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:298
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:224
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:315
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:150
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:357
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:762
Dialog for displaying query results of S57 objects.
Definition tc_win.h:44
IDX_entry * GetCurrentIDX() const
Definition tc_win.h:72
Represents a single point in a track.
Definition track.h:59
wxDateTime GetCreateTime(void)
Retrieves the creation timestamp of a track point as a wxDateTime object.
Definition track.cpp:138
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:144
Represents a track, which is a series of connected track points.
Definition track.h:117
Definition undo.h:58
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:56
void SetBoxes()
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp: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.
#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.