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