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
479 m_canvas_scale_factor = 1.;
480
481 m_canvas_width = 1000;
482
483 m_overzoomTextWidth = 0;
484 m_overzoomTextHeight = 0;
485
486 // Create the default world chart
487 pWorldBackgroundChart = new GSHHSChart;
488 gShapeBasemap.Reset();
489
490 // Create the default depth unit emboss maps
491 m_pEM_Feet = NULL;
492 m_pEM_Meters = NULL;
493 m_pEM_Fathoms = NULL;
494
495 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
496
497 m_pEM_OverZoom = NULL;
498 SetOverzoomFont();
499 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
500
501 // Build icons for tide/current points
502 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
503 m_bmTideDay =
504 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
505
506 // Dusk
507 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
508
509 // Night
510 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
511
512 // Build Dusk/Night ownship icons
513 double factor_dusk = 0.5;
514 double factor_night = 0.25;
515
516 // Red
517 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
518
519 int rimg_width = m_os_image_red_day.GetWidth();
520 int rimg_height = m_os_image_red_day.GetHeight();
521
522 m_os_image_red_dusk = m_os_image_red_day.Copy();
523 m_os_image_red_night = m_os_image_red_day.Copy();
524
525 for (int iy = 0; iy < rimg_height; iy++) {
526 for (int ix = 0; ix < rimg_width; ix++) {
527 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
528 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
529 m_os_image_red_day.GetGreen(ix, iy),
530 m_os_image_red_day.GetBlue(ix, iy));
531 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
532 hsv.value = hsv.value * factor_dusk;
533 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
534 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
535
536 hsv = wxImage::RGBtoHSV(rgb);
537 hsv.value = hsv.value * factor_night;
538 nrgb = wxImage::HSVtoRGB(hsv);
539 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
540 }
541 }
542 }
543
544 // Grey
545 m_os_image_grey_day =
546 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
547
548 int gimg_width = m_os_image_grey_day.GetWidth();
549 int gimg_height = m_os_image_grey_day.GetHeight();
550
551 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
552 m_os_image_grey_night = m_os_image_grey_day.Copy();
553
554 for (int iy = 0; iy < gimg_height; iy++) {
555 for (int ix = 0; ix < gimg_width; ix++) {
556 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
557 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
558 m_os_image_grey_day.GetGreen(ix, iy),
559 m_os_image_grey_day.GetBlue(ix, iy));
560 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
561 hsv.value = hsv.value * factor_dusk;
562 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
563 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
564
565 hsv = wxImage::RGBtoHSV(rgb);
566 hsv.value = hsv.value * factor_night;
567 nrgb = wxImage::HSVtoRGB(hsv);
568 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
569 }
570 }
571 }
572
573 // Yellow
574 m_os_image_yellow_day = m_os_image_red_day.Copy();
575
576 gimg_width = m_os_image_yellow_day.GetWidth();
577 gimg_height = m_os_image_yellow_day.GetHeight();
578
579 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
580 m_os_image_yellow_night = m_os_image_red_day.Copy();
581
582 for (int iy = 0; iy < gimg_height; iy++) {
583 for (int ix = 0; ix < gimg_width; ix++) {
584 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
585 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
586 m_os_image_yellow_day.GetGreen(ix, iy),
587 m_os_image_yellow_day.GetBlue(ix, iy));
588 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
589 hsv.hue += 60. / 360.; // shift to yellow
590 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
591 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
592
593 hsv = wxImage::RGBtoHSV(rgb);
594 hsv.value = hsv.value * factor_dusk;
595 hsv.hue += 60. / 360.; // shift to yellow
596 nrgb = wxImage::HSVtoRGB(hsv);
597 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
598
599 hsv = wxImage::RGBtoHSV(rgb);
600 hsv.hue += 60. / 360.; // shift to yellow
601 hsv.value = hsv.value * factor_night;
602 nrgb = wxImage::HSVtoRGB(hsv);
603 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
604 }
605 }
606 }
607
608 // Set initial pointers to ownship images
609 m_pos_image_red = &m_os_image_red_day;
610 m_pos_image_yellow = &m_os_image_yellow_day;
611 m_pos_image_grey = &m_os_image_grey_day;
612
613 SetUserOwnship();
614
615 m_pBrightPopup = NULL;
616
617#ifdef ocpnUSE_GL
618 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
619#endif
620
621 SetupGridFont();
622
623 m_Piano = new Piano(this);
624
625 m_bShowCompassWin = true;
626 m_Compass = new ocpnCompass(this);
627 m_Compass->SetScaleFactor(g_compass_scalefactor);
628 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
629
630 if (IsPrimaryCanvas()) {
631 m_notification_button = new NotificationButton(this);
632 m_notification_button->SetScaleFactor(g_compass_scalefactor);
633 m_notification_button->Show(true);
634 }
635
636 m_pianoFrozen = false;
637
638 SetMinSize(wxSize(200, 200));
639
640 m_displayScale = 1.0;
641#if defined(__WXOSX__) || defined(__WXGTK3__)
642 // Support scaled HDPI displays.
643 m_displayScale = GetContentScaleFactor();
644#endif
645 VPoint.SetPixelScale(m_displayScale);
646
647#ifdef HAVE_WX_GESTURE_EVENTS
648 // if (!m_glcc)
649 {
650 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
651 wxLogError("Failed to enable touch events");
652 }
653
654 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
655
656 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
657 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
658
659 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
660 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
661
662 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
663 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
664
665 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
666 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
667 }
668#endif
669
670 // Listen for notification events
671 auto &noteman = NotificationManager::GetInstance();
672
673 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
674 evt_notificationlist_change_listener.Listen(
675 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
676 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
677 if (m_NotificationsList && m_NotificationsList->IsShown()) {
678 m_NotificationsList->ReloadNotificationList();
679 }
680 Refresh();
681 });
682}
683
684ChartCanvas::~ChartCanvas() {
685 delete pThumbDIBShow;
686
687 // Delete Cursors
688 delete pCursorLeft;
689 delete pCursorRight;
690 delete pCursorUp;
691 delete pCursorDown;
692 delete pCursorArrow;
693 delete pCursorPencil;
694 delete pCursorCross;
695
696 delete pPanTimer;
697 delete pMovementTimer;
698 delete pMovementStopTimer;
699 delete pCurTrackTimer;
700 delete pRotDefTimer;
701 delete m_DoubleClickTimer;
702
703 delete m_pTrackRolloverWin;
704 delete m_pRouteRolloverWin;
705 delete m_pAISRolloverWin;
706 delete m_pBrightPopup;
707
708 delete m_pCIWin;
709
710 delete pscratch_bm;
711
712 m_dc_route.SelectObject(wxNullBitmap);
713 delete proute_bm;
714
715 delete pWorldBackgroundChart;
716 delete pss_overlay_bmp;
717
718 delete m_pEM_Feet;
719 delete m_pEM_Meters;
720 delete m_pEM_Fathoms;
721
722 delete m_pEM_OverZoom;
723 // delete m_pEM_CM93Offset;
724
725 delete m_prot_bm;
726
727 delete m_pos_image_user_day;
728 delete m_pos_image_user_dusk;
729 delete m_pos_image_user_night;
730 delete m_pos_image_user_grey_day;
731 delete m_pos_image_user_grey_dusk;
732 delete m_pos_image_user_grey_night;
733 delete m_pos_image_user_yellow_day;
734 delete m_pos_image_user_yellow_dusk;
735 delete m_pos_image_user_yellow_night;
736
737 delete undo;
738#ifdef ocpnUSE_GL
739 if (!g_bdisable_opengl) {
740 delete m_glcc;
741
742#if wxCHECK_VERSION(2, 9, 0)
743 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
744#endif
745 }
746#endif
747
748 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
749 // wx tries to deliver events to this canvas during destroy.
750 MUIBar *muiBar = m_muiBar;
751 m_muiBar = 0;
752 delete muiBar;
753 delete m_pQuilt;
754 delete m_pCurrentStack;
755 delete m_Compass;
756 delete m_Piano;
757}
758
759void ChartCanvas::SetupGridFont() {
760 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
761 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
762 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
763 m_pgridFont = FontMgr::Get().FindOrCreateFont(
764 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
765 FALSE, wxString("Arial"));
766}
767
768void ChartCanvas::RebuildCursors() {
769 delete pCursorLeft;
770 delete pCursorRight;
771 delete pCursorUp;
772 delete pCursorDown;
773 delete pCursorArrow;
774 delete pCursorPencil;
775 delete pCursorCross;
776
777 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
778 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
779
780 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
781
782 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
783 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
784 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
785 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
786 wxImage ICursorPencil =
787 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
788 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
789
790#if !defined(__WXMSW__) && !defined(__WXQT__)
791 ICursorLeft.ConvertAlphaToMask(128);
792 ICursorRight.ConvertAlphaToMask(128);
793 ICursorUp.ConvertAlphaToMask(128);
794 ICursorDown.ConvertAlphaToMask(128);
795 ICursorPencil.ConvertAlphaToMask(10);
796 ICursorCross.ConvertAlphaToMask(10);
797#endif
798
799 if (ICursorLeft.Ok()) {
800 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
801 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
802 pCursorLeft = new wxCursor(ICursorLeft);
803 } else
804 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
805
806 if (ICursorRight.Ok()) {
807 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
808 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
809 pCursorRight = new wxCursor(ICursorRight);
810 } else
811 pCursorRight = new wxCursor(wxCURSOR_ARROW);
812
813 if (ICursorUp.Ok()) {
814 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
815 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
816 pCursorUp = new wxCursor(ICursorUp);
817 } else
818 pCursorUp = new wxCursor(wxCURSOR_ARROW);
819
820 if (ICursorDown.Ok()) {
821 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
822 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
823 pCursorDown = new wxCursor(ICursorDown);
824 } else
825 pCursorDown = new wxCursor(wxCURSOR_ARROW);
826
827 if (ICursorPencil.Ok()) {
828 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
829 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
830 pCursorPencil = new wxCursor(ICursorPencil);
831 } else
832 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
833
834 if (ICursorCross.Ok()) {
835 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
836 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
837 pCursorCross = new wxCursor(ICursorCross);
838 } else
839 pCursorCross = new wxCursor(wxCURSOR_ARROW);
840
841 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
842 pPlugIn_Cursor = NULL;
843}
844
845void ChartCanvas::CanvasApplyLocale() {
846 CreateDepthUnitEmbossMaps(m_cs);
847 CreateOZEmbossMapData(m_cs);
848}
849
850void ChartCanvas::SetupGlCanvas() {
851#ifndef __ANDROID__
852#ifdef ocpnUSE_GL
853 if (!g_bdisable_opengl) {
854 if (g_bopengl) {
855 wxLogMessage("Creating glChartCanvas");
856 m_glcc = new glChartCanvas(this);
857
858 // We use one context for all GL windows, so that textures etc will be
859 // automatically shared
860 if (IsPrimaryCanvas()) {
861 // qDebug() << "Creating Primary Context";
862
863 // wxGLContextAttrs ctxAttr;
864 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
865 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
866 // NULL, &ctxAttr);
867 wxGLContext *pctx = new wxGLContext(m_glcc);
868 m_glcc->SetContext(pctx);
869 g_pGLcontext = pctx; // Save a copy of the common context
870 } else {
871#ifdef __WXOSX__
872 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
873#else
874 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
875 // saved common context
876#endif
877 }
878 }
879 }
880#endif
881#endif
882
883#ifdef __ANDROID__ // ocpnUSE_GL
884 if (!g_bdisable_opengl) {
885 if (g_bopengl) {
886 // qDebug() << "SetupGlCanvas";
887 wxLogMessage("Creating glChartCanvas");
888
889 // We use one context for all GL windows, so that textures etc will be
890 // automatically shared
891 if (IsPrimaryCanvas()) {
892 qDebug() << "Creating Primary glChartCanvas";
893
894 // wxGLContextAttrs ctxAttr;
895 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
896 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
897 // NULL, &ctxAttr);
898 m_glcc = new glChartCanvas(this);
899
900 wxGLContext *pctx = new wxGLContext(m_glcc);
901 m_glcc->SetContext(pctx);
902 g_pGLcontext = pctx; // Save a copy of the common context
903 m_glcc->m_pParentCanvas = this;
904 // m_glcc->Reparent(this);
905 } else {
906 qDebug() << "Creating Secondary glChartCanvas";
907 // QGLContext *pctx =
908 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
909 // << "pctx: " << pctx;
910
911 m_glcc = new glChartCanvas(
912 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
913 // m_glcc = new glChartCanvas(this, pctx); //Shared
914 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
915 wxGLContext *pwxctx = new wxGLContext(m_glcc);
916 m_glcc->SetContext(pwxctx);
917 m_glcc->m_pParentCanvas = this;
918 // m_glcc->Reparent(this);
919 }
920 }
921 }
922#endif
923}
924
925void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
926 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
927
928 // On Android, we get a KillFocus on just about every keystroke.
929 // Why?
930#ifdef __ANDROID__
931 return;
932#endif
933
934 // Special logic:
935 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
936 // canvas focus. Why??? Who knows... So, we provide for this case by
937 // starting a timer if required to actually Finish() a route on a legitimate
938 // focus change, but not if the focus is quickly regained ( <20 msec.) on
939 // this canvas.
940#ifdef __WXOSX__
941 if (m_routeState && m_FinishRouteOnKillFocus)
942 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
943#else
944 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
945#endif
946}
947
948void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
949 m_routeFinishTimer.Stop();
950
951 // Try to keep the global top-line menubar selections up to date with the
952 // current "focus" canvas
953 gFrame->UpdateGlobalMenuItems(this);
954
955 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
956}
957
958void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
959 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
960}
961
962#ifdef HAVE_WX_GESTURE_EVENTS
963void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
964#ifdef __ANDROID__
965 /* we defer the popup menu call upon the leftup event
966 else the menu disappears immediately,
967 (see
968 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
969 */
970 m_popupWanted = true;
971#else
972 m_inLongPress = !g_bhide_context_menus;
973
974 // Send a synthetic mouse left-up event to sync the mouse pan logic.
975 m_menuPos = event.GetPosition();
976 wxMouseEvent ev(wxEVT_LEFT_UP);
977 ev.m_x = m_menuPos.x;
978 ev.m_y = m_menuPos.y;
979 wxPostEvent(this, ev);
980
981 // In touch mode, send a "RIGHT CLICK" event, for plugins
982 if (g_btouch) {
983 wxMouseEvent ev_right_click(wxEVT_RIGHT_DOWN);
984 ev_right_click.m_x = m_menuPos.x;
985 ev_right_click.m_y = m_menuPos.y;
986 MouseEvent(ev_right_click);
987 }
988#endif
989}
990
991void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
992 // not implemented yet
993}
994
995void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
996
997void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
998
999void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1000#ifdef __WXGTK__
1001 long dt = m_sw_left_up.Time() - m_sw_up_time;
1002 m_sw_up_time = m_sw_left_up.Time();
1003
1004 // printf(" dt %ld\n",dt);
1005 if (dt < 5) {
1006 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1007 // ignore it.
1008 return;
1009 }
1010#endif
1011 // printf("Left_UP\n");
1012
1013 wxPoint pos = event.GetPosition();
1014
1015 m_leftdown = false;
1016
1017 if (!m_popupWanted) {
1018 wxMouseEvent ev(wxEVT_LEFT_UP);
1019 ev.m_x = pos.x;
1020 ev.m_y = pos.y;
1021 MouseEvent(ev);
1022 return;
1023 }
1024
1025 m_popupWanted = false;
1026
1027 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1028 ev.m_x = pos.x;
1029 ev.m_y = pos.y;
1030
1031 MouseEvent(ev);
1032}
1033
1034void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1035 m_leftdown = true;
1036
1037 // Detect and manage multiple left-downs coming from GTK mouse emulation
1038#ifdef __WXGTK__
1039 long dt = m_sw_left_down.Time() - m_sw_down_time;
1040 m_sw_down_time = m_sw_left_down.Time();
1041
1042 // printf("Left_DOWN_Entry: dt: %ld\n", dt);
1043
1044 // In touch mode, GTK mouse emulation will send duplicate mouse-down events.
1045 // The timing between the two events is dependent upon the wxWidgets
1046 // message queue status, and the processing time required for intervening
1047 // events.
1048 // We detect and remove the duplicate events by measuring the elapsed time
1049 // between arrival of events.
1050 // Choose a duplicate detection time long enough to catch worst case time lag
1051 // between duplicating events, but considerably shorter than the nominal
1052 // "intentional double-click" time interval defined generally as 350 msec.
1053 if (dt < 100) { // 10 taps per sec. is about the maximum human rate.
1054 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1055 // ignore it.
1056 return;
1057 }
1058#endif
1059
1060 // printf("Left_DOWN\n");
1061
1062 // detect and manage double-tap
1063#ifdef __WXGTK__
1064 int max_double_click_distance = wxSystemSettings::GetMetric(wxSYS_DCLICK_X) *
1065 2; // Use system setting for distance
1066 wxRect tap_area(m_lastTapPos.x - max_double_click_distance,
1067 m_lastTapPos.y - max_double_click_distance,
1068 max_double_click_distance * 2, max_double_click_distance * 2);
1069
1070 // A new tap has started, check if it's close enough and in time
1071 if (m_tap_timer.IsRunning() && tap_area.Contains(event.GetPosition())) {
1072 // printf(" TapBump 1\n");
1073 m_tap_count += 1;
1074 } else {
1075 // printf(" TapSet 1\n");
1076 m_tap_count = 1;
1077 m_lastTapPos = event.GetPosition();
1078 m_tap_timer.StartOnce(
1079 350); //(wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC));
1080 }
1081
1082 if (m_tap_count == 2) {
1083 // printf(" Doubletap detected\n");
1084 m_tap_count = 0; // Reset after a double-tap
1085
1086 wxMouseEvent ev(wxEVT_LEFT_DCLICK);
1087 ev.m_x = event.m_x;
1088 ev.m_y = event.m_y;
1089 // wxPostEvent(this, ev);
1090 MouseEvent(ev);
1091 return;
1092 }
1093
1094#endif
1095
1096 MouseEvent(event);
1097}
1098
1099void ChartCanvas::OnMotion(wxMouseEvent &event) {
1100 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1101 dragging, upon simple click, and without the OnLeftDown event before Thus,
1102 this consists in skiping it, and setting the leftdown bit according to a
1103 status that we trust */
1104 event.m_leftDown = m_leftdown;
1105 MouseEvent(event);
1106}
1107
1108void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1109 /* there are spurious end zoom events upon right-click */
1110 if (event.IsGestureEnd()) return;
1111
1112 double factor = event.GetZoomFactor();
1113
1114 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1115 m_oldVPSScale = GetVPScale();
1116 }
1117
1118 double current_vps = GetVPScale();
1119 double wanted_factor = m_oldVPSScale / current_vps * factor;
1120
1121 ZoomCanvas(wanted_factor, true, false);
1122
1123 // Allow combined zoom/pan operation
1124 if (event.IsGestureStart()) {
1125 m_zoomStartPoint = event.GetPosition();
1126 } else {
1127 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1128 PanCanvas(-delta.x, -delta.y);
1129 m_zoomStartPoint = event.GetPosition();
1130 }
1131}
1132
1133void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1134
1135void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1136 DoRotateCanvas(0.0);
1137}
1138#endif /* HAVE_WX_GESTURE_EVENTS */
1139
1140void ChartCanvas::OnTapTimer(wxTimerEvent &event) {
1141 // printf("tap timer %d\n", m_tap_count);
1142 m_tap_count = 0;
1143}
1144
1145void ChartCanvas::OnMenuTimer(wxTimerEvent &event) {
1146 m_FinishRouteOnKillFocus = false;
1147 CallPopupMenu(m_menuPos.x, m_menuPos.y);
1148 m_FinishRouteOnKillFocus = true;
1149}
1150
1151void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1152 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1153 m_vLat = pcc->iLat;
1154 m_vLon = pcc->iLon;
1155
1156 m_restore_dbindex = pcc->DBindex;
1157 m_bFollow = pcc->bFollow;
1158 if (pcc->GroupID < 0) pcc->GroupID = 0;
1159
1160 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1161 m_groupIndex = 0;
1162 else
1163 m_groupIndex = pcc->GroupID;
1164
1165 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1166
1167 ShowTides(pcc->bShowTides);
1168 ShowCurrents(pcc->bShowCurrents);
1169
1170 SetShowDepthUnits(pcc->bShowDepthUnits);
1171 SetShowGrid(pcc->bShowGrid);
1172 SetShowOutlines(pcc->bShowOutlines);
1173
1174 SetShowAIS(pcc->bShowAIS);
1175 SetAttenAIS(pcc->bAttenAIS);
1176
1177 // ENC options
1178 SetShowENCText(pcc->bShowENCText);
1179 m_encDisplayCategory = pcc->nENCDisplayCategory;
1180 m_encShowDepth = pcc->bShowENCDepths;
1181 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1182 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1183 m_encShowLights = pcc->bShowENCLights;
1184 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1185 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1186 m_encShowDataQual = pcc->bShowENCDataQuality;
1187
1188 bool courseUp = pcc->bCourseUp;
1189 bool headUp = pcc->bHeadUp;
1190 m_upMode = NORTH_UP_MODE;
1191 if (courseUp)
1192 m_upMode = COURSE_UP_MODE;
1193 else if (headUp)
1194 m_upMode = HEAD_UP_MODE;
1195
1196 m_bLookAhead = pcc->bLookahead;
1197
1198 m_singleChart = NULL;
1199}
1200
1201void ChartCanvas::ApplyGlobalSettings() {
1202 // GPS compas window
1203 if (m_Compass) {
1204 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1205 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1206 }
1207 if (m_notification_button) m_notification_button->UpdateStatus();
1208}
1209
1210void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1211 bool groupOK = CheckGroup(m_groupIndex);
1212
1213 if (!groupOK) {
1214 SetGroupIndex(m_groupIndex, true);
1215 }
1216}
1217
1218void ChartCanvas::SetShowGPS(bool bshow) {
1219 if (m_bShowGPS != bshow) {
1220 delete m_Compass;
1221 m_Compass = new ocpnCompass(this, bshow);
1222 m_Compass->SetScaleFactor(g_compass_scalefactor);
1223 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1224 }
1225 m_bShowGPS = bshow;
1226}
1227
1228void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1229 m_bShowCompassWin = bshow;
1230 if (m_Compass) {
1231 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1232 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1233 }
1234}
1235
1236int ChartCanvas::GetPianoHeight() {
1237 int height = 0;
1238 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1239
1240 return height;
1241}
1242
1243void ChartCanvas::ConfigureChartBar() {
1244 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1245
1246 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1247 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1248
1249 if (GetQuiltMode()) {
1250 m_Piano->SetRoundedRectangles(true);
1251 }
1252 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1253 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1254 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1255}
1256
1257void ChartCanvas::ShowTides(bool bShow) {
1258 gFrame->LoadHarmonics();
1259
1260 if (ptcmgr->IsReady()) {
1261 SetbShowTide(bShow);
1262
1263 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1264 } else {
1265 wxLogMessage("Chart1::Event...TCMgr Not Available");
1266 SetbShowTide(false);
1267 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1268 }
1269
1270 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1271 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1272
1273 // TODO
1274 // if( GetbShowTide() ) {
1275 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1276 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1277 // update
1278 // } else
1279 // FrameTCTimer.Stop();
1280}
1281
1282void ChartCanvas::ShowCurrents(bool bShow) {
1283 gFrame->LoadHarmonics();
1284
1285 if (ptcmgr->IsReady()) {
1286 SetbShowCurrent(bShow);
1287 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1288 } else {
1289 wxLogMessage("Chart1::Event...TCMgr Not Available");
1290 SetbShowCurrent(false);
1291 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1292 }
1293
1294 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1295 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1296
1297 // TODO
1298 // if( GetbShowCurrent() ) {
1299 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1300 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1301 // update
1302 // } else
1303 // FrameTCTimer.Stop();
1304}
1305
1306// TODO
1307static ChartDummy *pDummyChart;
1308
1311
1312void ChartCanvas::canvasRefreshGroupIndex() { SetGroupIndex(m_groupIndex); }
1313
1314void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1315 SetAlertString("");
1316
1317 int new_index = index;
1318 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1319
1320 bool bgroup_override = false;
1321 int old_group_index = new_index;
1322
1323 if (!CheckGroup(new_index)) {
1324 new_index = 0;
1325 bgroup_override = true;
1326 }
1327
1328 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1329 new_index = index;
1330
1331 // Get the currently displayed chart native scale, and the current ViewPort
1332 int current_chart_native_scale = GetCanvasChartNativeScale();
1333 ViewPort vp = GetVP();
1334
1335 m_groupIndex = new_index;
1336
1337 // Are there ENCs in this group
1338 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1339
1340 // Update the MUIBar for ENC availability
1341 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1342
1343 // Allow the chart database to pre-calculate the MBTile inclusion test
1344 // boolean...
1345 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1346
1347 // Invalidate the "sticky" chart on group change, since it might not be in
1348 // the new group
1349 g_sticky_chart = -1;
1350
1351 // We need a chartstack and quilt to figure out which chart to open in the
1352 // new group
1353 UpdateCanvasOnGroupChange();
1354
1355 int dbi_now = -1;
1356 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1357
1358 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1359
1360 // If a new reference chart is indicated, set a good scale for it.
1361 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1362 double best_scale = GetBestStartScale(dbi_hint, vp);
1363 SetVPScale(best_scale);
1364 }
1365
1366 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1367
1368 // Refresh the canvas, selecting the "best" chart,
1369 // applying the prior ViewPort exactly
1370 canvasChartsRefresh(dbi_hint);
1371
1372 UpdateCanvasControlBar();
1373
1374 if (!autoSwitch && bgroup_override) {
1375 // show a short timed message box
1376 wxString msg(_("Group \""));
1377
1378 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1379 msg += pGroup->m_group_name;
1380
1381 msg += _("\" is empty.");
1382
1383 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1384
1385 return;
1386 }
1387
1388 // Message box is deferred so that canvas refresh occurs properly before
1389 // dialog
1390 if (bgroup_override) {
1391 wxString msg(_("Group \""));
1392
1393 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1394 msg += pGroup->m_group_name;
1395
1396 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1397
1398 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1399 }
1400}
1401
1402bool ChartCanvas::CheckGroup(int igroup) {
1403 if (!ChartData) return true; // Not known yet...
1404
1405 if (igroup == 0) return true; // "all charts" is always OK
1406
1407 if (igroup < 0) // negative group is an error
1408 return false;
1409
1410 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1411
1412 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1413 // and auto-shift to group 0
1414 return false;
1415
1416 for (const auto &elem : pGroup->m_element_array) {
1417 for (unsigned int ic = 0;
1418 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1419 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1420 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1421
1422 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1423 }
1424 }
1425
1426 // If necessary, check for GSHHS
1427 for (const auto &elem : pGroup->m_element_array) {
1428 const wxString &element_root = elem.m_element_name;
1429 wxString test_string = "GSHH";
1430 if (element_root.Upper().Contains(test_string)) return true;
1431 }
1432
1433 return false;
1434}
1435
1436void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1437 if (!ChartData) return;
1438
1439 AbstractPlatform::ShowBusySpinner();
1440
1441 double old_scale = GetVPScale();
1442 InvalidateQuilt();
1443 SetQuiltRefChart(-1);
1444
1445 m_singleChart = NULL;
1446
1447 // delete m_pCurrentStack;
1448 // m_pCurrentStack = NULL;
1449
1450 // Build a new ChartStack
1451 if (!m_pCurrentStack) {
1452 m_pCurrentStack = new ChartStack;
1453 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1454 }
1455
1456 if (-1 != dbi_hint) {
1457 if (GetQuiltMode()) {
1458 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1459 SetQuiltRefChart(dbi_hint);
1460 } else {
1461 // Open the saved chart
1462 ChartBase *pTentative_Chart;
1463 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1464
1465 if (pTentative_Chart) {
1466 /* m_singleChart is always NULL here, (set above) should this go before
1467 * that? */
1468 if (m_singleChart) m_singleChart->Deactivate();
1469
1470 m_singleChart = pTentative_Chart;
1471 m_singleChart->Activate();
1472
1473 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1474 GetpCurrentStack(), m_singleChart->GetFullPath());
1475 }
1476 }
1477
1478 // refresh_Piano();
1479 } else {
1480 // Select reference chart from the stack, as though clicked by user
1481 // Make it the smallest scale chart on the stack
1482 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1483 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1484 SetQuiltRefChart(selected_index);
1485 }
1486
1487 // Validate the correct single chart, or set the quilt mode as appropriate
1488 SetupCanvasQuiltMode();
1489 if (!GetQuiltMode() && m_singleChart == 0) {
1490 // use a dummy like in DoChartUpdate
1491 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1492 m_singleChart = pDummyChart;
1493 SetVPScale(old_scale);
1494 }
1495
1496 ReloadVP();
1497
1498 UpdateCanvasControlBar();
1499 UpdateGPSCompassStatusBox(true);
1500
1501 SetCursor(wxCURSOR_ARROW);
1502
1503 AbstractPlatform::HideBusySpinner();
1504}
1505
1506bool ChartCanvas::DoCanvasUpdate() {
1507 double tLat, tLon; // Chart Stack location
1508 double vpLat, vpLon; // ViewPort location
1509 bool blong_jump = false;
1510 meters_to_shift = 0;
1511 dir_to_shift = 0;
1512
1513 bool bNewChart = false;
1514 bool bNewView = false;
1515 bool bCanvasChartAutoOpen = true; // debugging
1516
1517 bool bNewPiano = false;
1518 bool bOpenSpecified;
1519 ChartStack LastStack;
1520 ChartBase *pLast_Ch;
1521
1522 ChartStack WorkStack;
1523
1524 if (bDBUpdateInProgress) return false;
1525 if (!ChartData) return false;
1526
1527 if (ChartData->IsBusy()) return false;
1528 if (m_chart_drag_inertia_active) return false;
1529
1530 // Startup case:
1531 // Quilting is enabled, but the last chart seen was not quiltable
1532 // In this case, drop to single chart mode, set persistence flag,
1533 // And open the specified chart
1534 // TODO implement this
1535 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1536 // if( GetQuiltMode() ) {
1537 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1538 // gFrame->ToggleQuiltMode();
1539 // m_bpersistent_quilt = true;
1540 // m_singleChart = NULL;
1541 // }
1542 // }
1543 // }
1544
1545 // If in auto-follow mode, use the current glat,glon to build chart
1546 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1547 // other means
1548
1549 if (m_bFollow) {
1550 tLat = gLat;
1551 tLon = gLon;
1552
1553 // Set the ViewPort center based on the OWNSHIP offset
1554 double dx = m_OSoffsetx;
1555 double dy = m_OSoffsety;
1556 double d_east = dx / GetVP().view_scale_ppm;
1557 double d_north = dy / GetVP().view_scale_ppm;
1558
1559 if (GetUpMode() == NORTH_UP_MODE) {
1560 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1561 } else {
1562 double offset_angle = atan2(d_north, d_east);
1563 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1564 double chart_angle = GetVPRotation();
1565 double target_angle = chart_angle + offset_angle;
1566 double d_east_mod = offset_distance * cos(target_angle);
1567 double d_north_mod = offset_distance * sin(target_angle);
1568 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1569 }
1570
1571 // on lookahead mode, adjust the vp center point
1572 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1573 double cog_to_use = gCog;
1574 if (g_btenhertz &&
1575 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1576 cog_to_use = gCog_gt;
1577 blong_jump = true;
1578 }
1579 if (!g_btenhertz) cog_to_use = g_COGAvg;
1580
1581 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1582
1583 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1584 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1585
1586 double pixel_delta_tent =
1587 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1588
1589 double pixel_delta = 0;
1590
1591 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1592 // avoid jumping of the vp center point during slow maneuvering, or at
1593 // anchor....
1594 if (!std::isnan(gSog)) {
1595 if (gSog < 2.0)
1596 pixel_delta = 0.;
1597 else
1598 pixel_delta = pixel_delta_tent;
1599 }
1600
1601 meters_to_shift = 0;
1602 dir_to_shift = 0;
1603 if (!std::isnan(gCog)) {
1604 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1605 dir_to_shift = cog_to_use;
1606 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1607 &vpLon);
1608 } else {
1609 vpLat = gLat;
1610 vpLon = gLon;
1611 }
1612 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1613 m_OSoffsetx = 0; // center ownship on loss of GPS
1614 m_OSoffsety = 0;
1615 vpLat = gLat;
1616 vpLon = gLon;
1617 }
1618
1619 } else {
1620 tLat = m_vLat;
1621 tLon = m_vLon;
1622 vpLat = m_vLat;
1623 vpLon = m_vLon;
1624 }
1625
1626 if (GetQuiltMode()) {
1627 int current_db_index = -1;
1628 if (m_pCurrentStack)
1629 current_db_index =
1630 m_pCurrentStack
1631 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1632 // chart dbIndex
1633 else
1634 m_pCurrentStack = new ChartStack;
1635
1636 // This logic added to enable opening a chart when there is no
1637 // previous chart indication, either from inital startup, or from adding
1638 // new chart directory
1639 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1640 m_pCurrentStack) {
1641 if (m_pCurrentStack->nEntry) {
1642 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1643 1); // smallest scale
1644 SelectQuiltRefdbChart(new_dbIndex, true);
1645 m_bautofind = false;
1646 }
1647 }
1648
1649 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1650 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1651
1652 if (m_bFirstAuto) {
1653 // Allow the chart database to pre-calculate the MBTile inclusion test
1654 // boolean...
1655 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1656
1657 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1658 // physical pixels. On standard DPI displays where logical = physical
1659 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1660 // logical pixels, this ratio would be 0.5.
1661 double proposed_scale_onscreen =
1662 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1663
1664 int initial_db_index = m_restore_dbindex;
1665 if (initial_db_index < 0) {
1666 if (m_pCurrentStack->nEntry) {
1667 initial_db_index =
1668 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1669 } else
1670 m_bautofind = true; // initial_db_index = 0;
1671 }
1672
1673 if (m_pCurrentStack->nEntry) {
1674 int initial_type = ChartData->GetDBChartType(initial_db_index);
1675
1676 // Check to see if the target new chart is quiltable as a reference
1677 // chart
1678
1679 if (!IsChartQuiltableRef(initial_db_index)) {
1680 // If it is not quiltable, then walk the stack up looking for a
1681 // satisfactory chart i.e. one that is quiltable and of the same type
1682 // XXX if there's none?
1683 int stack_index = 0;
1684
1685 if (stack_index >= 0) {
1686 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1687 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1688 if (IsChartQuiltableRef(test_db_index) &&
1689 (initial_type ==
1690 ChartData->GetDBChartType(initial_db_index))) {
1691 initial_db_index = test_db_index;
1692 break;
1693 }
1694 stack_index++;
1695 }
1696 }
1697 }
1698
1699 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1700 if (pc) {
1701 SetQuiltRefChart(initial_db_index);
1702 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1703 }
1704 }
1705 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1706 // just GetVPScale(), so I'm not sure why it's necessary to define the
1707 // proposed_scale_onscreen variable.
1708 bNewView |= SetViewPoint(vpLat, vpLon,
1709 GetCanvasScaleFactor() / proposed_scale_onscreen,
1710 0, GetVPRotation());
1711 }
1712 // Measure rough jump distance if in bfollow mode
1713 // No good reason to do smooth pan for
1714 // jump distance more than one screen width at scale.
1715 bool super_jump = false;
1716 if (m_bFollow) {
1717 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1718 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1719 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1720 }
1721#if 0
1722 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead && !g_btouch && !m_bzooming) {
1723 int nstep = 5;
1724 if (blong_jump) nstep = 20;
1725 StartTimedMovementVP(vpLat, vpLon, nstep);
1726 } else
1727#endif
1728 {
1729 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1730 }
1731
1732 goto update_finish;
1733 }
1734
1735 // Single Chart Mode from here....
1736 pLast_Ch = m_singleChart;
1737 ChartTypeEnum new_open_type;
1738 ChartFamilyEnum new_open_family;
1739 if (pLast_Ch) {
1740 new_open_type = pLast_Ch->GetChartType();
1741 new_open_family = pLast_Ch->GetChartFamily();
1742 } else {
1743 new_open_type = CHART_TYPE_KAP;
1744 new_open_family = CHART_FAMILY_RASTER;
1745 }
1746
1747 bOpenSpecified = m_bFirstAuto;
1748
1749 // Make sure the target stack is valid
1750 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1751
1752 // Build a chart stack based on tLat, tLon
1753 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1754 m_groupIndex)) { // Bogus Lat, Lon?
1755 if (NULL == pDummyChart) {
1756 pDummyChart = new ChartDummy;
1757 bNewChart = true;
1758 }
1759
1760 if (m_singleChart)
1761 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1762
1763 m_singleChart = pDummyChart;
1764
1765 // If the current viewpoint is invalid, set the default scale to
1766 // something reasonable.
1767 double set_scale = GetVPScale();
1768 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1769
1770 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1771
1772 // If the chart stack has just changed, there is new status
1773 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1774 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1775 bNewPiano = true;
1776 bNewChart = true;
1777 }
1778 }
1779
1780 // Copy the new (by definition empty) stack into the target stack
1781 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1782
1783 goto update_finish;
1784 }
1785
1786 // Check to see if Chart Stack has changed
1787 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1788 // New chart stack, so...
1789 bNewPiano = true;
1790
1791 // Save a copy of the current stack
1792 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1793
1794 // Copy the new stack into the target stack
1795 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1796
1797 // Is Current Chart in new stack?
1798
1799 int tEntry = -1;
1800 if (NULL != m_singleChart) // this handles startup case
1801 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1802 m_singleChart->GetFullPath());
1803
1804 if (tEntry != -1) { // m_singleChart is in the new stack
1805 m_pCurrentStack->CurrentStackEntry = tEntry;
1806 bNewChart = false;
1807 }
1808
1809 else // m_singleChart is NOT in new stack
1810 { // So, need to open a new chart
1811 // Find the largest scale raster chart that opens OK
1812
1813 ChartBase *pProposed = NULL;
1814
1815 if (bCanvasChartAutoOpen) {
1816 bool search_direction =
1817 false; // default is to search from lowest to highest
1818 int start_index = 0;
1819
1820 // A special case: If panning at high scale, open largest scale
1821 // chart first
1822 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1823 (LastStack.nEntry == 0)) {
1824 search_direction = true;
1825 start_index = m_pCurrentStack->nEntry - 1;
1826 }
1827
1828 // Another special case, open specified index on program start
1829 if (bOpenSpecified) {
1830 search_direction = false;
1831 start_index = 0;
1832 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1833 start_index = 0;
1834
1835 new_open_type = CHART_TYPE_DONTCARE;
1836 }
1837
1838 pProposed = ChartData->OpenStackChartConditional(
1839 m_pCurrentStack, start_index, search_direction, new_open_type,
1840 new_open_family);
1841
1842 // Try to open other types/families of chart in some priority
1843 if (NULL == pProposed)
1844 pProposed = ChartData->OpenStackChartConditional(
1845 m_pCurrentStack, start_index, search_direction,
1846 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1847
1848 if (NULL == pProposed)
1849 pProposed = ChartData->OpenStackChartConditional(
1850 m_pCurrentStack, start_index, search_direction,
1851 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1852
1853 bNewChart = true;
1854
1855 } // bCanvasChartAutoOpen
1856
1857 else
1858 pProposed = NULL;
1859
1860 // If no go, then
1861 // Open a Dummy Chart
1862 if (NULL == pProposed) {
1863 if (NULL == pDummyChart) {
1864 pDummyChart = new ChartDummy;
1865 bNewChart = true;
1866 }
1867
1868 if (pLast_Ch)
1869 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1870
1871 pProposed = pDummyChart;
1872 }
1873
1874 // Arriving here, pProposed points to an opened chart, or NULL.
1875 if (m_singleChart) m_singleChart->Deactivate();
1876 m_singleChart = pProposed;
1877
1878 if (m_singleChart) {
1879 m_singleChart->Activate();
1880 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1881 m_pCurrentStack, m_singleChart->GetFullPath());
1882 }
1883 } // need new chart
1884
1885 // Arriving here, m_singleChart is opened and OK, or NULL
1886 if (NULL != m_singleChart) {
1887 // Setup the view using the current scale
1888 double set_scale = GetVPScale();
1889
1890 // If the current viewpoint is invalid, set the default scale to
1891 // something reasonable.
1892 if (!GetVP().IsValid())
1893 set_scale = 1. / 20000.;
1894 else { // otherwise, match scale if elected.
1895 double proposed_scale_onscreen;
1896
1897 if (m_bFollow) { // autoset the scale only if in autofollow
1898 double new_scale_ppm =
1899 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1900 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1901 } else
1902 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1903
1904 // This logic will bring a new chart onscreen at roughly twice the true
1905 // paper scale equivalent. Note that first chart opened on application
1906 // startup (bOpenSpecified = true) will open at the config saved scale
1907 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1908 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1909 double equivalent_vp_scale =
1910 GetCanvasScaleFactor() / proposed_scale_onscreen;
1911 double new_scale_ppm =
1912 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1913 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1914 }
1915
1916 if (m_bFollow) { // bounds-check the scale only if in autofollow
1917 proposed_scale_onscreen =
1918 wxMin(proposed_scale_onscreen,
1919 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1920 GetCanvasWidth()));
1921 proposed_scale_onscreen =
1922 wxMax(proposed_scale_onscreen,
1923 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1925 }
1926
1927 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1928 }
1929
1930 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1931 m_singleChart->GetChartSkew() * PI / 180.,
1932 GetVPRotation());
1933 }
1934 } // new stack
1935
1936 else // No change in Chart Stack
1937 {
1938 if ((m_bFollow) && m_singleChart)
1939 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1940 m_singleChart->GetChartSkew() * PI / 180.,
1941 GetVPRotation());
1942 }
1943
1944update_finish:
1945
1946 // TODO
1947 // if( bNewPiano ) UpdateControlBar();
1948
1949 m_bFirstAuto = false; // Auto open on program start
1950
1951 // If we need a Refresh(), do it here...
1952 // But don't duplicate a Refresh() done by SetViewPoint()
1953 if (bNewChart && !bNewView) Refresh(false);
1954
1955#ifdef ocpnUSE_GL
1956 // If a new chart, need to invalidate gl viewport for refresh
1957 // so the fbo gets flushed
1958 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1959#endif
1960
1961 return bNewChart | bNewView;
1962}
1963
1964void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1965 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1966
1967 SetQuiltRefChart(db_index);
1968 if (ChartData) {
1969 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1970 if (pc) {
1971 if (b_autoscale) {
1972 double best_scale_ppm = GetBestVPScale(pc);
1973 SetVPScale(best_scale_ppm);
1974 }
1975 } else
1976 SetQuiltRefChart(-1);
1977 } else
1978 SetQuiltRefChart(-1);
1979}
1980
1981void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1982 std::vector<int> piano_chart_index_array =
1983 GetQuiltExtendedStackdbIndexArray();
1984 int current_db_index = piano_chart_index_array[selected_index];
1985
1986 SelectQuiltRefdbChart(current_db_index);
1987}
1988
1989double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1990 if (pchart) {
1991 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1992
1993 if ((g_bPreserveScaleOnX) ||
1994 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
1995 double new_scale_ppm = GetVPScale();
1996 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1997 } else {
1998 // This logic will bring the new chart onscreen at roughly twice the true
1999 // paper scale equivalent.
2000 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2001 double equivalent_vp_scale =
2002 GetCanvasScaleFactor() / proposed_scale_onscreen;
2003 double new_scale_ppm =
2004 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2005 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2006 }
2007
2008 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2009 // set. Otherwise, we get severe performance problems on all platforms
2010
2011 double max_underzoom_multiplier = 2.0;
2012 if (GetVP().b_quilt) {
2013 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2014 pchart->GetChartType(),
2015 pchart->GetChartFamily());
2016 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2017 }
2018
2019 proposed_scale_onscreen = wxMin(
2020 proposed_scale_onscreen,
2021 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2022 max_underzoom_multiplier);
2023
2024 // And, do not allow excessive overzoom either
2025 proposed_scale_onscreen =
2026 wxMax(proposed_scale_onscreen,
2027 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2028
2029 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2030 } else
2031 return 1.0;
2032}
2033
2034void ChartCanvas::SetupCanvasQuiltMode() {
2035 if (GetQuiltMode()) // going to quilt mode
2036 {
2037 ChartData->LockCache();
2038
2039 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2040
2041 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2042
2043 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2044 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2045 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2046 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2047
2048 m_Piano->SetRoundedRectangles(true);
2049
2050 // Select the proper Ref chart
2051 int target_new_dbindex = -1;
2052 if (m_pCurrentStack) {
2053 target_new_dbindex =
2054 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2055
2056 if (-1 != target_new_dbindex) {
2057 if (!IsChartQuiltableRef(target_new_dbindex)) {
2058 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2059 int type = ChartData->GetDBChartType(target_new_dbindex);
2060
2061 // walk the stack up looking for a satisfactory chart
2062 int stack_index = m_pCurrentStack->CurrentStackEntry;
2063
2064 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2065 (stack_index >= 0)) {
2066 int proj_tent = ChartData->GetDBChartProj(
2067 m_pCurrentStack->GetDBIndex(stack_index));
2068 int type_tent = ChartData->GetDBChartType(
2069 m_pCurrentStack->GetDBIndex(stack_index));
2070
2071 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2072 if ((proj == proj_tent) && (type_tent == type)) {
2073 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2074 break;
2075 }
2076 }
2077 stack_index++;
2078 }
2079 }
2080 }
2081 }
2082
2083 if (IsChartQuiltableRef(target_new_dbindex))
2084 SelectQuiltRefdbChart(target_new_dbindex,
2085 false); // Try not to allow a scale change
2086 else
2087 SelectQuiltRefdbChart(-1, false);
2088
2089 m_singleChart = NULL; // Bye....
2090
2091 // Re-qualify the quilt reference chart selection
2092 AdjustQuiltRefChart();
2093
2094 // Restore projection type saved on last quilt mode toggle
2095 // TODO
2096 // if(g_sticky_projection != -1)
2097 // GetVP().SetProjectionType(g_sticky_projection);
2098 // else
2099 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2100 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2101
2102 } else // going to SC Mode
2103 {
2104 std::vector<int> empty_array;
2105 m_Piano->SetActiveKeyArray(empty_array);
2106 m_Piano->SetNoshowIndexArray(empty_array);
2107 m_Piano->SetEclipsedIndexArray(empty_array);
2108
2109 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2110 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2111 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2112 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2113 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2114
2115 m_Piano->SetRoundedRectangles(false);
2116 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2117 }
2118
2119 // When shifting from quilt to single chart mode, select the "best" single
2120 // chart to show
2121 if (!GetQuiltMode()) {
2122 if (ChartData && ChartData->IsValid()) {
2123 UnlockQuilt();
2124
2125 double tLat, tLon;
2126 if (m_bFollow == true) {
2127 tLat = gLat;
2128 tLon = gLon;
2129 } else {
2130 tLat = m_vLat;
2131 tLon = m_vLon;
2132 }
2133
2134 if (!m_singleChart) {
2135 // Build a temporary chart stack based on tLat, tLon
2136 ChartStack TempStack;
2137 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2138 m_groupIndex);
2139
2140 // Iterate over the quilt charts actually shown, looking for the
2141 // largest scale chart that will be in the new chartstack.... This
2142 // will (almost?) always be the reference chart....
2143
2144 ChartBase *Candidate_Chart = NULL;
2145 int cur_max_scale = (int)1e8;
2146
2147 ChartBase *pChart = GetFirstQuiltChart();
2148 while (pChart) {
2149 // Is this pChart in new stack?
2150 int tEntry =
2151 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2152 if (tEntry != -1) {
2153 if (pChart->GetNativeScale() < cur_max_scale) {
2154 Candidate_Chart = pChart;
2155 cur_max_scale = pChart->GetNativeScale();
2156 }
2157 }
2158 pChart = GetNextQuiltChart();
2159 }
2160
2161 m_singleChart = Candidate_Chart;
2162
2163 // If the quilt is empty, there is no "best" chart.
2164 // So, open the smallest scale chart in the current stack
2165 if (NULL == m_singleChart) {
2166 m_singleChart = ChartData->OpenStackChartConditional(
2167 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2168 CHART_FAMILY_DONTCARE);
2169 }
2170 }
2171
2172 // Invalidate all the charts in the quilt,
2173 // as any cached data may be region based and not have fullscreen coverage
2174 InvalidateAllQuiltPatchs();
2175
2176 if (m_singleChart) {
2177 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2178 std::vector<int> one_array;
2179 one_array.push_back(dbi);
2180 m_Piano->SetActiveKeyArray(one_array);
2181 }
2182
2183 if (m_singleChart) {
2184 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2185 }
2186 }
2187 // Invalidate the current stack so that it will be rebuilt on next tick
2188 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2189 }
2190}
2191
2192bool ChartCanvas::IsTempMenuBarEnabled() {
2193#ifdef __WXMSW__
2194 int major;
2195 wxGetOsVersion(&major);
2196 return (major >
2197 5); // For Windows, function is only available on Vista and above
2198#else
2199 return true;
2200#endif
2201}
2202
2203double ChartCanvas::GetCanvasRangeMeters() {
2204 int width, height;
2205 GetSize(&width, &height);
2206 int minDimension = wxMin(width, height);
2207
2208 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2209 range *= cos(GetVP().clat * PI / 180.);
2210 return range;
2211}
2212
2213void ChartCanvas::SetCanvasRangeMeters(double range) {
2214 int width, height;
2215 GetSize(&width, &height);
2216 int minDimension = wxMin(width, height);
2217
2218 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2219 SetVPScale(scale_ppm / 2);
2220}
2221
2222bool ChartCanvas::SetUserOwnship() {
2223 // Look for user defined ownship image
2224 // This may be found in the shared data location along with other user
2225 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2226 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2227 double factor_dusk = 0.5;
2228 double factor_night = 0.25;
2229
2230 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2231 m_pos_image_user_day = new wxImage;
2232 *m_pos_image_user_day = pbmp->ConvertToImage();
2233 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2234
2235 int gimg_width = m_pos_image_user_day->GetWidth();
2236 int gimg_height = m_pos_image_user_day->GetHeight();
2237
2238 // Make dusk and night images
2239 m_pos_image_user_dusk = new wxImage;
2240 m_pos_image_user_night = new wxImage;
2241
2242 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2243 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2244
2245 for (int iy = 0; iy < gimg_height; iy++) {
2246 for (int ix = 0; ix < gimg_width; ix++) {
2247 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2248 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2249 m_pos_image_user_day->GetGreen(ix, iy),
2250 m_pos_image_user_day->GetBlue(ix, iy));
2251 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2252 hsv.value = hsv.value * factor_dusk;
2253 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2254 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2255 nrgb.blue);
2256
2257 hsv = wxImage::RGBtoHSV(rgb);
2258 hsv.value = hsv.value * factor_night;
2259 nrgb = wxImage::HSVtoRGB(hsv);
2260 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2261 nrgb.blue);
2262 }
2263 }
2264 }
2265
2266 // Make some alternate greyed out day/dusk/night images
2267 m_pos_image_user_grey_day = new wxImage;
2268 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2269
2270 m_pos_image_user_grey_dusk = new wxImage;
2271 m_pos_image_user_grey_night = new wxImage;
2272
2273 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2274 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2275
2276 for (int iy = 0; iy < gimg_height; iy++) {
2277 for (int ix = 0; ix < gimg_width; ix++) {
2278 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2279 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2280 m_pos_image_user_grey_day->GetGreen(ix, iy),
2281 m_pos_image_user_grey_day->GetBlue(ix, iy));
2282 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2283 hsv.value = hsv.value * factor_dusk;
2284 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2285 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2286 nrgb.blue);
2287
2288 hsv = wxImage::RGBtoHSV(rgb);
2289 hsv.value = hsv.value * factor_night;
2290 nrgb = wxImage::HSVtoRGB(hsv);
2291 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2292 nrgb.blue);
2293 }
2294 }
2295 }
2296
2297 // Make a yellow image for rendering under low accuracy chart conditions
2298 m_pos_image_user_yellow_day = new wxImage;
2299 m_pos_image_user_yellow_dusk = new wxImage;
2300 m_pos_image_user_yellow_night = new wxImage;
2301
2302 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2303 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2304 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2305
2306 for (int iy = 0; iy < gimg_height; iy++) {
2307 for (int ix = 0; ix < gimg_width; ix++) {
2308 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2309 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2310 m_pos_image_user_grey_day->GetGreen(ix, iy),
2311 m_pos_image_user_grey_day->GetBlue(ix, iy));
2312
2313 // Simply remove all "blue" from the greyscaled image...
2314 // so, what is not black becomes yellow.
2315 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2316 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2317 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2318
2319 hsv = wxImage::RGBtoHSV(rgb);
2320 hsv.value = hsv.value * factor_dusk;
2321 nrgb = wxImage::HSVtoRGB(hsv);
2322 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2323
2324 hsv = wxImage::RGBtoHSV(rgb);
2325 hsv.value = hsv.value * factor_night;
2326 nrgb = wxImage::HSVtoRGB(hsv);
2327 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2328 0);
2329 }
2330 }
2331 }
2332
2333 return true;
2334 } else
2335 return false;
2336}
2337
2339 m_display_size_mm = size;
2340
2341 // int sx, sy;
2342 // wxDisplaySize( &sx, &sy );
2343
2344 // Calculate logical pixels per mm for later reference.
2345 wxSize sd = g_Platform->getDisplaySize();
2346 double horizontal = sd.x;
2347 // Set DPI (Win) scale factor
2348 g_scaler = g_Platform->GetDisplayDIPMult(this);
2349
2350 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2351 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2352
2353 if (ps52plib) {
2354 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2355 ps52plib->SetPPMM(m_pix_per_mm);
2356 }
2357
2358 wxString msg;
2359 msg.Printf(
2360 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2361 "%d:%d ",
2362 m_display_size_mm, sd.x, sd.y);
2363 wxLogMessage(msg);
2364
2365 int ssx, ssy;
2366 ssx = g_monitor_info[g_current_monitor].width;
2367 ssy = g_monitor_info[g_current_monitor].height;
2368 msg.Printf("monitor size: %d %d", ssx, ssy);
2369 wxLogMessage(msg);
2370
2371 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2372}
2373#if 0
2374void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2375{
2376 wxString msg(event.m_string.c_str(), wxConvUTF8);
2377 // if cpus are removed between runs
2378 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2379 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2380 }
2381
2382 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2383 {
2384 compress_msg_array.RemoveAt(event.thread);
2385 compress_msg_array.Insert( msg, event.thread);
2386 }
2387 else
2388 compress_msg_array.Add(msg);
2389
2390
2391 wxString combined_msg;
2392 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2393 combined_msg += compress_msg_array[i];
2394 combined_msg += "\n";
2395 }
2396
2397 bool skip = false;
2398 pprog->Update(pprog_count, combined_msg, &skip );
2399 pprog->SetSize(pprog_size);
2400 if(skip)
2401 b_skipout = skip;
2402}
2403#endif
2404void ChartCanvas::InvalidateGL() {
2405 if (!m_glcc) return;
2406#ifdef ocpnUSE_GL
2407 if (g_bopengl) m_glcc->Invalidate();
2408#endif
2409 if (m_Compass) m_Compass->UpdateStatus(true);
2410}
2411
2412int ChartCanvas::GetCanvasChartNativeScale() {
2413 int ret = 1;
2414 if (!VPoint.b_quilt) {
2415 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2416 } else
2417 ret = (int)m_pQuilt->GetRefNativeScale();
2418
2419 return ret;
2420}
2421
2422ChartBase *ChartCanvas::GetChartAtCursor() {
2423 ChartBase *target_chart;
2424 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2425 target_chart = m_singleChart;
2426 else if (VPoint.b_quilt)
2427 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2428 else
2429 target_chart = NULL;
2430 return target_chart;
2431}
2432
2433ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2434 ChartBase *target_chart;
2435 if (VPoint.b_quilt)
2436 target_chart =
2437 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2438 else
2439 target_chart = NULL;
2440 return target_chart;
2441}
2442
2443int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2444 int new_dbIndex = -1;
2445 if (!VPoint.b_quilt) {
2446 if (m_pCurrentStack) {
2447 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2448 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2449 if (sc >= scale) {
2450 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2451 break;
2452 }
2453 }
2454 }
2455 } else {
2456 // Using the current quilt, select a useable reference chart
2457 // Said chart will be in the extended (possibly full-screen) stack,
2458 // And will have a scale equal to or just greater than the stipulated
2459 // value
2460 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2461 if (im > 0) {
2462 for (unsigned int is = 0; is < im; is++) {
2463 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2464 m_pQuilt->GetExtendedStackIndexArray()[is]);
2465 if ((m.Scale_ge(
2466 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2467 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2468 break;
2469 }
2470 }
2471 }
2472 }
2473
2474 return new_dbIndex;
2475}
2476
2477void ChartCanvas::EnablePaint(bool b_enable) {
2478 m_b_paint_enable = b_enable;
2479#ifdef ocpnUSE_GL
2480 if (m_glcc) m_glcc->EnablePaint(b_enable);
2481#endif
2482}
2483
2484bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2485
2486void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2487
2488std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2489 return m_pQuilt->GetQuiltIndexArray();
2490 ;
2491}
2492
2493void ChartCanvas::SetQuiltMode(bool b_quilt) {
2494 VPoint.b_quilt = b_quilt;
2495 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2496}
2497
2498bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2499
2500int ChartCanvas::GetQuiltReferenceChartIndex() {
2501 return m_pQuilt->GetRefChartdbIndex();
2502}
2503
2504void ChartCanvas::InvalidateAllQuiltPatchs() {
2505 m_pQuilt->InvalidateAllQuiltPatchs();
2506}
2507
2508ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2509 return m_pQuilt->GetLargestScaleChart();
2510}
2511
2512ChartBase *ChartCanvas::GetFirstQuiltChart() {
2513 return m_pQuilt->GetFirstChart();
2514}
2515
2516ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2517
2518int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2519
2520void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2521 m_pQuilt->SetHiliteIndex(dbIndex);
2522}
2523
2524void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2525 m_pQuilt->SetHiliteIndexArray(hilite_array);
2526}
2527
2528void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2529 m_pQuilt->ClearHiliteIndexArray();
2530}
2531
2532std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2533 bool flag2) {
2534 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2535}
2536
2537int ChartCanvas::GetQuiltRefChartdbIndex() {
2538 return m_pQuilt->GetRefChartdbIndex();
2539}
2540
2541std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2542 return m_pQuilt->GetExtendedStackIndexArray();
2543}
2544
2545std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2546 return m_pQuilt->GetFullscreenIndexArray();
2547}
2548
2549std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2550 return m_pQuilt->GetEclipsedStackIndexArray();
2551}
2552
2553void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2554
2555double ChartCanvas::GetQuiltMaxErrorFactor() {
2556 return m_pQuilt->GetMaxErrorFactor();
2557}
2558
2559bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2560 return m_pQuilt->IsChartQuiltableRef(db_index);
2561}
2562
2563bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2564 double chartMaxScale =
2565 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2566 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2567}
2568
2569void ChartCanvas::StartMeasureRoute() {
2570 if (!m_routeState) { // no measure tool if currently creating route
2571 if (m_bMeasure_Active) {
2572 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2573 m_pMeasureRoute = NULL;
2574 }
2575
2576 m_bMeasure_Active = true;
2577 m_nMeasureState = 1;
2578 m_bDrawingRoute = false;
2579
2580 SetCursor(*pCursorPencil);
2581 Refresh();
2582 }
2583}
2584
2585void ChartCanvas::CancelMeasureRoute() {
2586 m_bMeasure_Active = false;
2587 m_nMeasureState = 0;
2588 m_bDrawingRoute = false;
2589
2590 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2591 m_pMeasureRoute = NULL;
2592
2593 SetCursor(*pCursorArrow);
2594}
2595
2596ViewPort &ChartCanvas::GetVP() { return VPoint; }
2597
2598void ChartCanvas::SetVP(ViewPort &vp) {
2599 VPoint = vp;
2600 VPoint.SetPixelScale(m_displayScale);
2601}
2602
2603// void ChartCanvas::SetFocus()
2604// {
2605// printf("set %d\n", m_canvasIndex);
2606// //wxWindow:SetFocus();
2607// }
2608
2609void ChartCanvas::TriggerDeferredFocus() {
2610 // #if defined(__WXGTK__) || defined(__WXOSX__)
2611
2612 m_deferredFocusTimer.Start(20, true);
2613
2614#if defined(__WXGTK__) || defined(__WXOSX__)
2615 gFrame->Raise();
2616#endif
2617
2618 // gFrame->Raise();
2619 // #else
2620 // SetFocus();
2621 // Refresh(true);
2622 // #endif
2623}
2624
2625void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2626 SetFocus();
2627 Refresh(true);
2628}
2629
2630void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2631 if (SendKeyEventToPlugins(event))
2632 return; // PlugIn did something, and does not want the canvas to do
2633 // anything else
2634
2635 int key_char = event.GetKeyCode();
2636 switch (key_char) {
2637 case '?':
2638 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2639 break;
2640 case '+':
2641 ZoomCanvas(g_plus_minus_zoom_factor, false);
2642 break;
2643 case '-':
2644 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2645 break;
2646 default:
2647 break;
2648 }
2649 if (g_benable_rotate) {
2650 switch (key_char) {
2651 case ']':
2652 RotateCanvas(1);
2653 Refresh();
2654 break;
2655
2656 case '[':
2657 RotateCanvas(-1);
2658 Refresh();
2659 break;
2660
2661 case '\\':
2662 DoRotateCanvas(0);
2663 break;
2664 }
2665 }
2666
2667 event.Skip();
2668}
2669
2670void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2671 if (SendKeyEventToPlugins(event))
2672 return; // PlugIn did something, and does not want the canvas to do
2673 // anything else
2674
2675 bool b_handled = false;
2676
2677 m_modkeys = event.GetModifiers();
2678
2679 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2680
2681#ifdef OCPN_ALT_MENUBAR
2682#ifndef __WXOSX__
2683 // If the permanent menubar is disabled, we show it temporarily when Alt is
2684 // pressed or when Alt + a letter is presssed (for the top-menu-level
2685 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2686 // some special cases.
2687 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2688 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2689 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2690 if (!g_bTempShowMenuBar) {
2691 g_bTempShowMenuBar = true;
2692 parent_frame->ApplyGlobalSettings(false);
2693 }
2694 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2695 event.Skip();
2696 return;
2697 }
2698 // If another key is pressed while Alt is down, do NOT toggle the menus when
2699 // Alt is released
2700 if (event.GetKeyCode() != WXK_ALT) {
2701 m_bMayToggleMenuBar = false;
2702 }
2703 }
2704#endif
2705#endif
2706
2707 // HOTKEYS
2708 switch (event.GetKeyCode()) {
2709 case WXK_TAB:
2710 // parent_frame->SwitchKBFocus( this );
2711 break;
2712
2713 case WXK_MENU:
2714 int x, y;
2715 event.GetPosition(&x, &y);
2716 m_FinishRouteOnKillFocus = false;
2717 CallPopupMenu(x, y);
2718 m_FinishRouteOnKillFocus = true;
2719 break;
2720
2721 case WXK_ALT:
2722 m_modkeys |= wxMOD_ALT;
2723 break;
2724
2725 case WXK_CONTROL:
2726 m_modkeys |= wxMOD_CONTROL;
2727 break;
2728
2729#ifdef __WXOSX__
2730 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2731 case WXK_RAW_CONTROL:
2732 m_modkeys |= wxMOD_RAW_CONTROL;
2733 break;
2734#endif
2735
2736 case WXK_LEFT:
2737 if (m_modkeys == wxMOD_CONTROL)
2738 parent_frame->DoStackDown(this);
2739 else if (g_bsmoothpanzoom) {
2740 StartTimedMovement();
2741 m_panx = -1;
2742 } else {
2743 PanCanvas(-panspeed, 0);
2744 }
2745 b_handled = true;
2746 break;
2747
2748 case WXK_UP:
2749 if (g_bsmoothpanzoom) {
2750 StartTimedMovement();
2751 m_pany = -1;
2752 } else
2753 PanCanvas(0, -panspeed);
2754 b_handled = true;
2755 break;
2756
2757 case WXK_RIGHT:
2758 if (m_modkeys == wxMOD_CONTROL)
2759 parent_frame->DoStackUp(this);
2760 else if (g_bsmoothpanzoom) {
2761 StartTimedMovement();
2762 m_panx = 1;
2763 } else
2764 PanCanvas(panspeed, 0);
2765 b_handled = true;
2766
2767 break;
2768
2769 case WXK_DOWN:
2770 if (g_bsmoothpanzoom) {
2771 StartTimedMovement();
2772 m_pany = 1;
2773 } else
2774 PanCanvas(0, panspeed);
2775 b_handled = true;
2776 break;
2777
2778 case WXK_F2:
2779 TogglebFollow();
2780 break;
2781
2782 case WXK_F3: {
2783 SetShowENCText(!GetShowENCText());
2784 Refresh(true);
2785 InvalidateGL();
2786 break;
2787 }
2788 case WXK_F4:
2789 if (!m_bMeasure_Active) {
2790 if (event.ShiftDown())
2791 m_bMeasure_DistCircle = true;
2792 else
2793 m_bMeasure_DistCircle = false;
2794
2795 StartMeasureRoute();
2796 } else {
2797 CancelMeasureRoute();
2798
2799 SetCursor(*pCursorArrow);
2800
2801 // SurfaceToolbar();
2802 InvalidateGL();
2803 Refresh(false);
2804 }
2805
2806 break;
2807
2808 case WXK_F5:
2809 parent_frame->ToggleColorScheme();
2810 gFrame->Raise();
2811 TriggerDeferredFocus();
2812 break;
2813
2814 case WXK_F6: {
2815 int mod = m_modkeys & wxMOD_SHIFT;
2816 if (mod != m_brightmod) {
2817 m_brightmod = mod;
2818 m_bbrightdir = !m_bbrightdir;
2819 }
2820
2821 if (!m_bbrightdir) {
2822 g_nbrightness -= 10;
2823 if (g_nbrightness <= MIN_BRIGHT) {
2824 g_nbrightness = MIN_BRIGHT;
2825 m_bbrightdir = true;
2826 }
2827 } else {
2828 g_nbrightness += 10;
2829 if (g_nbrightness >= MAX_BRIGHT) {
2830 g_nbrightness = MAX_BRIGHT;
2831 m_bbrightdir = false;
2832 }
2833 }
2834
2835 SetScreenBrightness(g_nbrightness);
2836 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2837
2838 SetFocus(); // just in case the external program steals it....
2839 gFrame->Raise(); // And reactivate the application main
2840
2841 break;
2842 }
2843
2844 case WXK_F7:
2845 parent_frame->DoStackDown(this);
2846 break;
2847
2848 case WXK_F8:
2849 parent_frame->DoStackUp(this);
2850 break;
2851
2852#ifndef __WXOSX__
2853 case WXK_F9: {
2854 ToggleCanvasQuiltMode();
2855 break;
2856 }
2857#endif
2858
2859 case WXK_F11:
2860 parent_frame->ToggleFullScreen();
2861 b_handled = true;
2862 break;
2863
2864 case WXK_F12: {
2865 if (m_modkeys == wxMOD_ALT) {
2866 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2867 } else {
2868 ToggleChartOutlines();
2869 }
2870 break;
2871 }
2872
2873 case WXK_PAUSE: // Drop MOB
2874 parent_frame->ActivateMOB();
2875 break;
2876
2877 // NUMERIC PAD
2878 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2879 case WXK_PAGEUP: {
2880 ZoomCanvas(g_plus_minus_zoom_factor, false);
2881 break;
2882 }
2883 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2884 case WXK_PAGEDOWN: {
2885 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2886 break;
2887 }
2888 case WXK_DELETE:
2889 case WXK_BACK:
2890 if (m_bMeasure_Active) {
2891 if (m_nMeasureState > 2) {
2892 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2893 m_pMeasureRoute->m_lastMousePointIndex =
2894 m_pMeasureRoute->GetnPoints();
2895 m_nMeasureState--;
2896 gFrame->RefreshAllCanvas();
2897 } else {
2898 CancelMeasureRoute();
2899 StartMeasureRoute();
2900 }
2901 }
2902 break;
2903 default:
2904 break;
2905 }
2906
2907 if (event.GetKeyCode() < 128) // ascii
2908 {
2909 int key_char = event.GetKeyCode();
2910
2911 // Handle both QWERTY and AZERTY keyboard separately for a few control
2912 // codes
2913 if (!g_b_assume_azerty) {
2914#ifdef __WXMAC__
2915 if (g_benable_rotate) {
2916 switch (key_char) {
2917 // On other platforms these are handled in OnKeyChar, which
2918 // (apparently) works better in some locales. On OS X it is better
2919 // to handle them here, since pressing Alt (which should change the
2920 // rotation speed) changes the key char and so prevents the keys
2921 // from working.
2922 case ']':
2923 RotateCanvas(1);
2924 b_handled = true;
2925 break;
2926
2927 case '[':
2928 RotateCanvas(-1);
2929 b_handled = true;
2930 break;
2931
2932 case '\\':
2933 DoRotateCanvas(0);
2934 b_handled = true;
2935 break;
2936 }
2937 }
2938#endif
2939 } else { // AZERTY
2940 switch (key_char) {
2941 case 43:
2942 ZoomCanvas(g_plus_minus_zoom_factor, false);
2943 break;
2944
2945 case 54: // '-' alpha/num pad
2946 // case 56: // '_' alpha/num pad
2947 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2948 break;
2949 }
2950 }
2951
2952#ifdef __WXOSX__
2953 // Ctrl+Cmd+F toggles fullscreen on macOS
2954 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
2955 m_modkeys & wxMOD_RAW_CONTROL) {
2956 parent_frame->ToggleFullScreen();
2957 return;
2958 }
2959#endif
2960
2961 if (event.ControlDown()) key_char -= 64;
2962
2963 if (key_char >= '0' && key_char <= '9')
2964 SetGroupIndex(key_char - '0');
2965 else
2966
2967 switch (key_char) {
2968 case 'A':
2969 SetShowENCAnchor(!GetShowENCAnchor());
2970 ReloadVP();
2971
2972 break;
2973
2974 case 'C':
2975 parent_frame->ToggleColorScheme();
2976 break;
2977
2978 case 'D': {
2979 int x, y;
2980 event.GetPosition(&x, &y);
2981 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
2982 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
2983 // First find out what kind of chart is being used
2984 if (!pPopupDetailSlider) {
2985 if (VPoint.b_quilt) {
2986 if (m_pQuilt) {
2987 if (m_pQuilt->GetChartAtPix(
2988 VPoint,
2989 wxPoint(
2990 x, y))) // = null if no chart loaded for this point
2991 {
2992 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2993 ->GetChartType();
2994 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2995 ->GetChartFamily();
2996 }
2997 }
2998 } else {
2999 if (m_singleChart) {
3000 ChartType = m_singleChart->GetChartType();
3001 ChartFam = m_singleChart->GetChartFamily();
3002 }
3003 }
3004 // If a charttype is found show the popupslider
3005 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3006 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3008 this, -1, ChartType, ChartFam,
3009 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3010 wxDefaultSize, wxSIMPLE_BORDER, "");
3012 }
3013 } else //( !pPopupDetailSlider ) close popupslider
3014 {
3016 pPopupDetailSlider = NULL;
3017 }
3018 break;
3019 }
3020
3021 case 'E':
3022 m_nmea_log->Show();
3023 m_nmea_log->Raise();
3024 break;
3025
3026 case 'L':
3027 SetShowENCLights(!GetShowENCLights());
3028 ReloadVP();
3029
3030 break;
3031
3032 case 'M':
3033 if (event.ShiftDown())
3034 m_bMeasure_DistCircle = true;
3035 else
3036 m_bMeasure_DistCircle = false;
3037
3038 StartMeasureRoute();
3039 break;
3040
3041 case 'N':
3042 if (g_bInlandEcdis && ps52plib) {
3043 SetENCDisplayCategory((_DisCat)STANDARD);
3044 }
3045 break;
3046
3047 case 'O':
3048 ToggleChartOutlines();
3049 break;
3050
3051 case 'Q':
3052 ToggleCanvasQuiltMode();
3053 break;
3054
3055 case 'P':
3056 parent_frame->ToggleTestPause();
3057 break;
3058 case 'R':
3059 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3060 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3061 g_iNavAidRadarRingsNumberVisible = 1;
3062 else if (!g_bNavAidRadarRingsShown &&
3063 g_iNavAidRadarRingsNumberVisible == 1)
3064 g_iNavAidRadarRingsNumberVisible = 0;
3065 break;
3066 case 'S':
3067 SetShowENCDepth(!m_encShowDepth);
3068 ReloadVP();
3069 break;
3070
3071 case 'T':
3072 SetShowENCText(!GetShowENCText());
3073 ReloadVP();
3074 break;
3075
3076 case 'U':
3077 SetShowENCDataQual(!GetShowENCDataQual());
3078 ReloadVP();
3079 break;
3080
3081 case 'V':
3082 m_bShowNavobjects = !m_bShowNavobjects;
3083 Refresh(true);
3084 break;
3085
3086 case 'W': // W Toggle CPA alarm
3087 ToggleCPAWarn();
3088
3089 break;
3090
3091 case 1: // Ctrl A
3092 TogglebFollow();
3093
3094 break;
3095
3096 case 2: // Ctrl B
3097 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3098 break;
3099
3100 case 13: // Ctrl M // Drop Marker at cursor
3101 {
3102 if (event.ControlDown()) gFrame->DropMarker(false);
3103 break;
3104 }
3105
3106 case 14: // Ctrl N - Activate next waypoint in a route
3107 {
3108 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3109 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3110 if ((indexActive + 1) <= r->GetnPoints()) {
3112 InvalidateGL();
3113 Refresh(false);
3114 }
3115 }
3116 break;
3117 }
3118
3119 case 15: // Ctrl O - Drop Marker at boat's position
3120 {
3121 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3122 break;
3123 }
3124
3125 case 32: // Special needs use space bar
3126 {
3127 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3128 break;
3129 }
3130
3131 case -32: // Ctrl Space // Drop MOB
3132 {
3133 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3134
3135 break;
3136 }
3137
3138 case -20: // Ctrl ,
3139 {
3140 parent_frame->DoSettings();
3141 break;
3142 }
3143 case 17: // Ctrl Q
3144 parent_frame->Close();
3145 return;
3146
3147 case 18: // Ctrl R
3148 StartRoute();
3149 return;
3150
3151 case 20: // Ctrl T
3152 if (NULL == pGoToPositionDialog) // There is one global instance of
3153 // the Go To Position Dialog
3155 pGoToPositionDialog->SetCanvas(this);
3156 pGoToPositionDialog->Show();
3157 break;
3158
3159 case 25: // Ctrl Y
3160 if (undo->AnythingToRedo()) {
3161 undo->RedoNextAction();
3162 InvalidateGL();
3163 Refresh(false);
3164 }
3165 break;
3166
3167 case 26:
3168 if (event.ShiftDown()) { // Shift-Ctrl-Z
3169 if (undo->AnythingToRedo()) {
3170 undo->RedoNextAction();
3171 InvalidateGL();
3172 Refresh(false);
3173 }
3174 } else { // Ctrl Z
3175 if (undo->AnythingToUndo()) {
3176 undo->UndoLastAction();
3177 InvalidateGL();
3178 Refresh(false);
3179 }
3180 }
3181 break;
3182
3183 case 27:
3184 // Generic break
3185 if (m_bMeasure_Active) {
3186 CancelMeasureRoute();
3187
3188 SetCursor(*pCursorArrow);
3189
3190 // SurfaceToolbar();
3191 gFrame->RefreshAllCanvas();
3192 }
3193
3194 if (m_routeState) // creating route?
3195 {
3196 FinishRoute();
3197 // SurfaceToolbar();
3198 InvalidateGL();
3199 Refresh(false);
3200 }
3201
3202 break;
3203
3204 case 7: // Ctrl G
3205 switch (gamma_state) {
3206 case (0):
3207 r_gamma_mult = 0;
3208 g_gamma_mult = 1;
3209 b_gamma_mult = 0;
3210 gamma_state = 1;
3211 break;
3212 case (1):
3213 r_gamma_mult = 1;
3214 g_gamma_mult = 0;
3215 b_gamma_mult = 0;
3216 gamma_state = 2;
3217 break;
3218 case (2):
3219 r_gamma_mult = 1;
3220 g_gamma_mult = 1;
3221 b_gamma_mult = 1;
3222 gamma_state = 0;
3223 break;
3224 }
3225 SetScreenBrightness(g_nbrightness);
3226
3227 break;
3228
3229 case 9: // Ctrl I
3230 if (event.ControlDown()) {
3231 m_bShowCompassWin = !m_bShowCompassWin;
3232 SetShowGPSCompassWindow(m_bShowCompassWin);
3233 Refresh(false);
3234 }
3235 break;
3236
3237 default:
3238 break;
3239
3240 } // switch
3241 }
3242
3243 // Allow OnKeyChar to catch the key events too.
3244 if (!b_handled) {
3245 event.Skip();
3246 }
3247}
3248
3249void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3250 if (SendKeyEventToPlugins(event))
3251 return; // PlugIn did something, and does not want the canvas to do
3252 // anything else
3253
3254 switch (event.GetKeyCode()) {
3255 case WXK_TAB:
3256 parent_frame->SwitchKBFocus(this);
3257 break;
3258
3259 case WXK_LEFT:
3260 case WXK_RIGHT:
3261 m_panx = 0;
3262 if (!m_pany) m_panspeed = 0;
3263 break;
3264
3265 case WXK_UP:
3266 case WXK_DOWN:
3267 m_pany = 0;
3268 if (!m_panx) m_panspeed = 0;
3269 break;
3270
3271 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3272 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3273 case WXK_PAGEUP:
3274 case WXK_PAGEDOWN:
3275 if (m_mustmove) DoMovement(m_mustmove);
3276
3277 m_zoom_factor = 1;
3278 break;
3279
3280 case WXK_ALT:
3281 m_modkeys &= ~wxMOD_ALT;
3282#ifdef OCPN_ALT_MENUBAR
3283#ifndef __WXOSX__
3284 // If the permanent menu bar is disabled, and we are not in the middle of
3285 // another key combo, then show the menu bar temporarily when Alt is
3286 // released (or hide it if already visible).
3287 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3288 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3289 parent_frame->ApplyGlobalSettings(false);
3290 }
3291 m_bMayToggleMenuBar = true;
3292#endif
3293#endif
3294 break;
3295
3296 case WXK_CONTROL:
3297 m_modkeys &= ~wxMOD_CONTROL;
3298 break;
3299 }
3300
3301 if (event.GetKeyCode() < 128) // ascii
3302 {
3303 int key_char = event.GetKeyCode();
3304
3305 // Handle both QWERTY and AZERTY keyboard separately for a few control
3306 // codes
3307 if (!g_b_assume_azerty) {
3308 switch (key_char) {
3309 case '+':
3310 case '=':
3311 case '-':
3312 case '_':
3313 case 54:
3314 case 56: // '_' alpha/num pad
3315 DoMovement(m_mustmove);
3316
3317 // m_zoom_factor = 1;
3318 break;
3319 case '[':
3320 case ']':
3321 DoMovement(m_mustmove);
3322 m_rotation_speed = 0;
3323 break;
3324 }
3325 } else {
3326 switch (key_char) {
3327 case 43:
3328 case 54: // '-' alpha/num pad
3329 case 56: // '_' alpha/num pad
3330 DoMovement(m_mustmove);
3331
3332 m_zoom_factor = 1;
3333 break;
3334 }
3335 }
3336 }
3337 event.Skip();
3338}
3339
3340void ChartCanvas::ToggleChartOutlines() {
3341 m_bShowOutlines = !m_bShowOutlines;
3342
3343 Refresh(false);
3344
3345#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3346 // needs a full refresh
3347 if (g_bopengl) InvalidateGL();
3348#endif
3349}
3350
3351void ChartCanvas::ToggleLookahead() {
3352 m_bLookAhead = !m_bLookAhead;
3353 m_OSoffsetx = 0; // center ownship
3354 m_OSoffsety = 0;
3355}
3356
3357void ChartCanvas::SetUpMode(int mode) {
3358 m_upMode = mode;
3359
3360 if (mode != NORTH_UP_MODE) {
3361 // Stuff the COGAvg table in case COGUp is selected
3362 double stuff = 0;
3363 if (!std::isnan(gCog)) stuff = gCog;
3364
3365 if (g_COGAvgSec > 0) {
3366 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3367 }
3368 g_COGAvg = stuff;
3369 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3370 } else {
3371 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3372 SetVPRotation(GetVPSkew());
3373 else
3374 SetVPRotation(0); /* reset to north up */
3375 }
3376
3377 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3378 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3379
3380 UpdateGPSCompassStatusBox(true);
3381 gFrame->DoChartUpdate();
3382}
3383
3384bool ChartCanvas::DoCanvasCOGSet() {
3385 if (GetUpMode() == NORTH_UP_MODE) return false;
3386 double cog_use = g_COGAvg;
3387 if (g_btenhertz) cog_use = gCog;
3388
3389 double rotation = 0;
3390 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3391 rotation = -gHdt * PI / 180.;
3392 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3393 rotation = -cog_use * PI / 180.;
3394
3395 SetVPRotation(rotation);
3396 return true;
3397}
3398
3399double easeOutCubic(double t) {
3400 // Starts quickly and slows down toward the end
3401 return 1.0 - pow(1.0 - t, 3.0);
3402}
3403
3404void ChartCanvas::StartChartDragInertia() {
3405 m_bChartDragging = false;
3406
3407 // Set some parameters
3408 m_chart_drag_inertia_time = 750; // msec
3409 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3410 m_last_elapsed = 0;
3411
3412 // Calculate ending drag velocity
3413 size_t n_vel = 10;
3414 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3415 int xacc = 0;
3416 int yacc = 0;
3417 double tacc = 0;
3418 size_t length = m_drag_vec_t.size();
3419 for (size_t i = 0; i < n_vel; i++) {
3420 xacc += m_drag_vec_x.at(length - 1 - i);
3421 yacc += m_drag_vec_y.at(length - 1 - i);
3422 tacc += m_drag_vec_t.at(length - 1 - i);
3423 }
3424
3425 if (tacc == 0) return;
3426
3427 double drag_velocity_x = xacc / tacc;
3428 double drag_velocity_y = yacc / tacc;
3429 // printf("drag total %d %d %g %g %g\n", xacc, yacc, tacc, drag_velocity_x,
3430 // drag_velocity_y);
3431
3432 // Abort inertia drag if velocity is very slow, preventing jitters on sloppy
3433 // touch tap.
3434 if ((fabs(drag_velocity_x) < 200) && (fabs(drag_velocity_y) < 200)) return;
3435
3436 m_chart_drag_velocity_x = drag_velocity_x;
3437 m_chart_drag_velocity_y = drag_velocity_y;
3438
3439 m_chart_drag_inertia_active = true;
3440 // First callback as fast as possible.
3441 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3442}
3443
3444void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3445 if (!m_chart_drag_inertia_active) return;
3446 // Calculate time fraction from 0..1
3447 wxLongLong now = wxGetLocalTimeMillis();
3448 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3449 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3450 if (t > 1.0) t = 1.0;
3451 double e = 1.0 - easeOutCubic(t); // 0..1
3452
3453 double dx =
3454 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3455 double dy =
3456 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3457
3458 m_last_elapsed = elapsed;
3459
3460 // Ensure that target destination lies on whole-pixel boundary
3461 // This allows the render engine to use a faster FBO copy method for drawing
3462 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3463 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3464 double inertia_lat, inertia_lon;
3465 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3466 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3467 // Check if ownship has moved off-screen
3468 if (!IsOwnshipOnScreen()) {
3469 m_bFollow = false; // update the follow flag
3470 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3471 UpdateFollowButtonState();
3472 m_OSoffsetx = 0;
3473 m_OSoffsety = 0;
3474 } else {
3475 m_OSoffsetx += dx;
3476 m_OSoffsety -= dy;
3477 }
3478
3479 Refresh(false);
3480
3481 // Stop condition
3482 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3483 m_chart_drag_inertia_timer.Stop();
3484
3485 // Disable chart pan movement logic
3486 m_target_lat = GetVP().clat;
3487 m_target_lon = GetVP().clon;
3488 m_pan_drag.x = m_pan_drag.y = 0;
3489 m_panx = m_pany = 0;
3490 m_chart_drag_inertia_active = false;
3491 DoCanvasUpdate();
3492
3493 } else {
3494 int target_redraw_interval = 40; // msec
3495 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3496 }
3497}
3498
3499void ChartCanvas::StopMovement() {
3500 m_panx = m_pany = 0;
3501 m_panspeed = 0;
3502 m_zoom_factor = 1;
3503 m_rotation_speed = 0;
3504 m_mustmove = 0;
3505#if 0
3506#if !defined(__WXGTK__) && !defined(__WXQT__)
3507 SetFocus();
3508 gFrame->Raise();
3509#endif
3510#endif
3511}
3512
3513/* instead of integrating in timer callbacks
3514 (which do not always get called fast enough)
3515 we can perform the integration of movement
3516 at each render frame based on the time change */
3517bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3518 // Start/restart the stop movement timer
3519 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3520
3521 if (!pMovementTimer->IsRunning()) {
3522 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3523 }
3524
3525 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3526 // already moving, gets called again because of key-repeat event
3527 return false;
3528 }
3529
3530 m_last_movement_time = wxDateTime::UNow();
3531
3532 return true;
3533}
3534void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3535 int nstep) {
3536 // Save the target
3537 m_target_lat = target_lat;
3538 m_target_lon = target_lon;
3539
3540 // Save the start point
3541 m_start_lat = GetVP().clat;
3542 m_start_lon = GetVP().clon;
3543
3544 m_VPMovementTimer.Start(1, true); // oneshot
3545 m_timed_move_vp_active = true;
3546 m_stvpc = 0;
3547 m_timedVP_step = nstep;
3548}
3549
3550void ChartCanvas::DoTimedMovementVP() {
3551 if (!m_timed_move_vp_active) return; // not active
3552 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3553 StopMovement();
3554 return;
3555 }
3556 // Stop condition
3557 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3558 double d2 =
3559 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3560 d2 = pow(d2, 0.5);
3561
3562 if (d2 < one_pix) {
3563 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3564 StopMovementVP();
3565 return;
3566 }
3567
3568 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3569 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3570 // StopMovementVP();
3571 // return;
3572 // }
3573
3574 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3575 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3576
3577 m_run_lat = new_lat;
3578 m_run_lon = new_lon;
3579
3580 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3581}
3582
3583void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3584
3585void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3586
3587void ChartCanvas::StartTimedMovementTarget() {}
3588
3589void ChartCanvas::DoTimedMovementTarget() {}
3590
3591void ChartCanvas::StopMovementTarget() {}
3592int ntm;
3593
3594void ChartCanvas::DoTimedMovement() {
3595 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3596 !m_rotation_speed)
3597 return; /* not moving */
3598
3599 wxDateTime now = wxDateTime::UNow();
3600 long dt = 0;
3601 if (m_last_movement_time.IsValid())
3602 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3603
3604 m_last_movement_time = now;
3605
3606 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3607 dt = 500;
3608
3609 DoMovement(dt);
3610}
3611
3613 /* if we get here quickly assume 1ms so that some movement occurs */
3614 if (dt == 0) dt = 1;
3615
3616 m_mustmove -= dt;
3617 if (m_mustmove < 0) m_mustmove = 0;
3618
3619 if (!m_inPinch) { // this stops compound zoom/pan
3620 if (m_pan_drag.x || m_pan_drag.y) {
3621 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3622 m_pan_drag.x = m_pan_drag.y = 0;
3623 }
3624
3625 if (m_panx || m_pany) {
3626 const double slowpan = .1, maxpan = 2;
3627 if (m_modkeys == wxMOD_ALT)
3628 m_panspeed = slowpan;
3629 else {
3630 m_panspeed += (double)dt / 500; /* apply acceleration */
3631 m_panspeed = wxMin(maxpan, m_panspeed);
3632 }
3633 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3634 }
3635 }
3636 if (m_zoom_factor != 1) {
3637 double alpha = 400, beta = 1.5;
3638 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3639
3640 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3641
3642 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3643
3644 // Try to hit the zoom target exactly.
3645 // if(m_wheelzoom_stop_oneshot > 0)
3646 {
3647 if (zoom_factor > 1) {
3648 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3649 zoom_factor = VPoint.chart_scale / m_zoom_target;
3650 }
3651
3652 else if (zoom_factor < 1) {
3653 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3654 zoom_factor = VPoint.chart_scale / m_zoom_target;
3655 }
3656 }
3657
3658 if (fabs(zoom_factor - 1) > 1e-4) {
3659 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3660 } else {
3661 StopMovement();
3662 }
3663
3664 if (m_wheelzoom_stop_oneshot > 0) {
3665 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3666 m_wheelzoom_stop_oneshot = 0;
3667 StopMovement();
3668 }
3669
3670 // Don't overshoot the zoom target.
3671 if (zoom_factor > 1) {
3672 if (VPoint.chart_scale <= m_zoom_target) {
3673 m_wheelzoom_stop_oneshot = 0;
3674 StopMovement();
3675 }
3676 } else if (zoom_factor < 1) {
3677 if (VPoint.chart_scale >= m_zoom_target) {
3678 m_wheelzoom_stop_oneshot = 0;
3679 StopMovement();
3680 }
3681 }
3682 }
3683 }
3684
3685 if (m_rotation_speed) { /* in degrees per second */
3686 double speed = m_rotation_speed;
3687 if (m_modkeys == wxMOD_ALT) speed /= 10;
3688 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3689 }
3690}
3691
3692void ChartCanvas::SetColorScheme(ColorScheme cs) {
3693 SetAlertString("");
3694
3695 // Setup ownship image pointers
3696 switch (cs) {
3697 case GLOBAL_COLOR_SCHEME_DAY:
3698 m_pos_image_red = &m_os_image_red_day;
3699 m_pos_image_grey = &m_os_image_grey_day;
3700 m_pos_image_yellow = &m_os_image_yellow_day;
3701 m_pos_image_user = m_pos_image_user_day;
3702 m_pos_image_user_grey = m_pos_image_user_grey_day;
3703 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3704 m_cTideBitmap = m_bmTideDay;
3705 m_cCurrentBitmap = m_bmCurrentDay;
3706
3707 break;
3708 case GLOBAL_COLOR_SCHEME_DUSK:
3709 m_pos_image_red = &m_os_image_red_dusk;
3710 m_pos_image_grey = &m_os_image_grey_dusk;
3711 m_pos_image_yellow = &m_os_image_yellow_dusk;
3712 m_pos_image_user = m_pos_image_user_dusk;
3713 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3714 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3715 m_cTideBitmap = m_bmTideDusk;
3716 m_cCurrentBitmap = m_bmCurrentDusk;
3717 break;
3718 case GLOBAL_COLOR_SCHEME_NIGHT:
3719 m_pos_image_red = &m_os_image_red_night;
3720 m_pos_image_grey = &m_os_image_grey_night;
3721 m_pos_image_yellow = &m_os_image_yellow_night;
3722 m_pos_image_user = m_pos_image_user_night;
3723 m_pos_image_user_grey = m_pos_image_user_grey_night;
3724 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3725 m_cTideBitmap = m_bmTideNight;
3726 m_cCurrentBitmap = m_bmCurrentNight;
3727 break;
3728 default:
3729 m_pos_image_red = &m_os_image_red_day;
3730 m_pos_image_grey = &m_os_image_grey_day;
3731 m_pos_image_yellow = &m_os_image_yellow_day;
3732 m_pos_image_user = m_pos_image_user_day;
3733 m_pos_image_user_grey = m_pos_image_user_grey_day;
3734 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3735 m_cTideBitmap = m_bmTideDay;
3736 m_cCurrentBitmap = m_bmCurrentDay;
3737 break;
3738 }
3739
3740 CreateDepthUnitEmbossMaps(cs);
3741 CreateOZEmbossMapData(cs);
3742
3743 // Set up fog effect base color
3744 m_fog_color = wxColor(
3745 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3746 float dim = 1.0;
3747 switch (cs) {
3748 case GLOBAL_COLOR_SCHEME_DUSK:
3749 dim = 0.5;
3750 break;
3751 case GLOBAL_COLOR_SCHEME_NIGHT:
3752 dim = 0.25;
3753 break;
3754 default:
3755 break;
3756 }
3757 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3758 m_fog_color.Blue() * dim);
3759
3760 // Really dark
3761#if 0
3762 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3763 SetBackgroundColour( wxColour(0,0,0) );
3764
3765 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3766 }
3767 else{
3768 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3769#ifndef __WXMAC__
3770 SetBackgroundColour( wxNullColour );
3771#endif
3772 }
3773#endif
3774
3775 // UpdateToolbarColorScheme(cs);
3776
3777 m_Piano->SetColorScheme(cs);
3778
3779 m_Compass->SetColorScheme(cs);
3780
3781 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3782
3783 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3784
3785 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3786 if (m_notification_button) {
3787 m_notification_button->SetColorScheme(cs);
3788 }
3789
3790#ifdef ocpnUSE_GL
3791 if (g_bopengl && m_glcc) {
3792 m_glcc->SetColorScheme(cs);
3793 g_glTextureManager->ClearAllRasterTextures();
3794 // m_glcc->FlushFBO();
3795 }
3796#endif
3797 SetbTCUpdate(true); // force re-render of tide/current locators
3798 m_brepaint_piano = true;
3799
3800 ReloadVP();
3801
3802 m_cs = cs;
3803}
3804
3805wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3806 wxImage img = Bitmap.ConvertToImage();
3807 int sx = img.GetWidth();
3808 int sy = img.GetHeight();
3809
3810 wxImage new_img(img);
3811
3812 for (int i = 0; i < sx; i++) {
3813 for (int j = 0; j < sy; j++) {
3814 if (!img.IsTransparent(i, j)) {
3815 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3816 (unsigned char)(img.GetGreen(i, j) * factor),
3817 (unsigned char)(img.GetBlue(i, j) * factor));
3818 }
3819 }
3820 }
3821
3822 wxBitmap ret = wxBitmap(new_img);
3823
3824 return ret;
3825}
3826
3827void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3828 int max) {
3829 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3830 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3831
3832 if (!m_pBrightPopup) {
3833 // Calculate size
3834 int x, y;
3835 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3836
3837 m_pBrightPopup = new TimedPopupWin(this, 3);
3838
3839 m_pBrightPopup->SetSize(x, y);
3840 m_pBrightPopup->Move(120, 120);
3841 }
3842
3843 int bmpsx = m_pBrightPopup->GetSize().x;
3844 int bmpsy = m_pBrightPopup->GetSize().y;
3845
3846 wxBitmap bmp(bmpsx, bmpsx);
3847 wxMemoryDC mdc(bmp);
3848
3849 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3850 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3851 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3852 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3853 mdc.Clear();
3854
3855 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3856
3857 mdc.SetFont(*pfont);
3858 wxString val;
3859
3860 if (brightness == max)
3861 val = "MAX";
3862 else if (brightness == min)
3863 val = "MIN";
3864 else
3865 val.Printf("%3d", brightness);
3866
3867 mdc.DrawText(val, 0, 0);
3868
3869 mdc.SelectObject(wxNullBitmap);
3870
3871 m_pBrightPopup->SetBitmap(bmp);
3872 m_pBrightPopup->Show();
3873 m_pBrightPopup->Refresh();
3874}
3875
3876void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3877 m_b_rot_hidef = true;
3878 ReloadVP();
3879}
3880
3881void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3882 if (!g_bRollover) return;
3883
3884 bool b_need_refresh = false;
3885
3886 wxSize win_size = GetSize() * m_displayScale;
3887 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3888
3889 // Handle the AIS Rollover Window first
3890 bool showAISRollover = false;
3891 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3892 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3893 SelectItem *pFind = pSelectAIS->FindSelection(
3894 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3895 if (pFind) {
3896 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3897 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3898
3899 if (ptarget) {
3900 showAISRollover = true;
3901
3902 if (NULL == m_pAISRolloverWin) {
3903 m_pAISRolloverWin = new RolloverWin(this);
3904 m_pAISRolloverWin->IsActive(false);
3905 b_need_refresh = true;
3906 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3907 m_AISRollover_MMSI != FoundAIS_MMSI) {
3908 // Sometimes the mouse moves fast enough to get over a new AIS
3909 // target before the one-shot has fired to remove the old target.
3910 // Result: wrong target data is shown.
3911 // Detect this case,close the existing rollover ASAP, and restart
3912 // the timer.
3913 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3914 m_pAISRolloverWin->IsActive(false);
3915 m_AISRollover_MMSI = 0;
3916 Refresh();
3917 return;
3918 }
3919
3920 m_AISRollover_MMSI = FoundAIS_MMSI;
3921
3922 if (!m_pAISRolloverWin->IsActive()) {
3923 wxString s = ptarget->GetRolloverString();
3924 m_pAISRolloverWin->SetString(s);
3925
3926 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3927 AIS_ROLLOVER, win_size);
3928 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3929 m_pAISRolloverWin->IsActive(true);
3930 b_need_refresh = true;
3931 }
3932 }
3933 } else {
3934 m_AISRollover_MMSI = 0;
3935 showAISRollover = false;
3936 }
3937 }
3938
3939 // Maybe turn the rollover off
3940 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3941 m_pAISRolloverWin->IsActive(false);
3942 m_AISRollover_MMSI = 0;
3943 b_need_refresh = true;
3944 }
3945
3946 // Now the Route info rollover
3947 // Show the route segment info
3948 bool showRouteRollover = false;
3949
3950 if (NULL == m_pRolloverRouteSeg) {
3951 // Get a list of all selectable sgements, and search for the first
3952 // visible segment as the rollover target.
3953
3954 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3955 SelectableItemList SelList = pSelect->FindSelectionList(
3956 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3957 auto node = SelList.begin();
3958 while (node != SelList.end()) {
3959 SelectItem *pFindSel = *node;
3960
3961 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3962
3963 if (pr && pr->IsVisible()) {
3964 m_pRolloverRouteSeg = pFindSel;
3965 showRouteRollover = true;
3966
3967 if (NULL == m_pRouteRolloverWin) {
3968 m_pRouteRolloverWin = new RolloverWin(this, 10);
3969 m_pRouteRolloverWin->IsActive(false);
3970 }
3971
3972 if (!m_pRouteRolloverWin->IsActive()) {
3973 wxString s;
3974 RoutePoint *segShow_point_a =
3975 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
3976 RoutePoint *segShow_point_b =
3977 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
3978
3979 double brg, dist;
3980 DistanceBearingMercator(
3981 segShow_point_b->m_lat, segShow_point_b->m_lon,
3982 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
3983
3984 if (!pr->m_bIsInLayer)
3985 s.Append(_("Route") + ": ");
3986 else
3987 s.Append(_("Layer Route: "));
3988
3989 if (pr->m_RouteNameString.IsEmpty())
3990 s.Append(_("(unnamed)"));
3991 else
3992 s.Append(pr->m_RouteNameString);
3993
3994 s << "\n"
3995 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
3996 << "\n"
3997 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
3998 << segShow_point_b->GetName() << "\n";
3999
4000 if (g_bShowTrue)
4001 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4002 (int)floor(brg + 0.5), 0x00B0);
4003 if (g_bShowMag) {
4004 double latAverage =
4005 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4006 double lonAverage =
4007 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4008 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4009
4010 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4011 (int)floor(varBrg + 0.5), 0x00B0);
4012 }
4013
4014 s << FormatDistanceAdaptive(dist);
4015
4016 // Compute and display cumulative distance from route start point to
4017 // current leg end point and RNG,TTG,ETA from ship to current leg end
4018 // point for active route
4019 double shiptoEndLeg = 0.;
4020 bool validActive = false;
4021 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4022 validActive = true;
4023
4024 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4025 auto node = pr->pRoutePointList->begin();
4026 RoutePoint *prp;
4027 float dist_to_endleg = 0;
4028 wxString t;
4029
4030 for (++node; node != pr->pRoutePointList->end(); ++node) {
4031 prp = *node;
4032 if (validActive)
4033 shiptoEndLeg += prp->m_seg_len;
4034 else if (prp->m_bIsActive)
4035 validActive = true;
4036 dist_to_endleg += prp->m_seg_len;
4037 if (prp->IsSame(segShow_point_a)) break;
4038 }
4039 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4040 }
4041 // write from ship to end selected leg point data if the route is
4042 // active
4043 if (validActive) {
4044 s << "\n"
4045 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4046 shiptoEndLeg +=
4048 ->GetCurrentRngToActivePoint(); // add distance from ship
4049 // to active point
4050 shiptoEndLeg +=
4051 segShow_point_b
4052 ->m_seg_len; // add the lenght of the selected leg
4053 s << FormatDistanceAdaptive(shiptoEndLeg);
4054 // ensure sog/cog are valid and vmg is positive to keep data
4055 // coherent
4056 double vmg = 0.;
4057 if (!std::isnan(gCog) && !std::isnan(gSog))
4058 vmg = gSog *
4059 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4060 PI / 180.);
4061 if (vmg > 0.) {
4062 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4063 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4064 s << " - "
4065 << wxString(ttg_sec > SECONDS_PER_DAY
4066 ? ttg_span.Format(_("%Dd %H:%M"))
4067 : ttg_span.Format(_("%H:%M")));
4068 wxDateTime dtnow, eta;
4069 eta = dtnow.SetToCurrent().Add(ttg_span);
4070 s << " - " << eta.Format("%b").Mid(0, 4)
4071 << eta.Format(" %d %H:%M");
4072 } else
4073 s << " ---- ----";
4074 }
4075 m_pRouteRolloverWin->SetString(s);
4076
4077 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4078 LEG_ROLLOVER, win_size);
4079 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4080 m_pRouteRolloverWin->IsActive(true);
4081 b_need_refresh = true;
4082 showRouteRollover = true;
4083 break;
4084 }
4085 } else {
4086 ++node;
4087 }
4088 }
4089 } else {
4090 // Is the cursor still in select radius, and not timed out?
4091 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4092 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4093 m_pRolloverRouteSeg))
4094 showRouteRollover = false;
4095 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4096 showRouteRollover = false;
4097 else
4098 showRouteRollover = true;
4099 }
4100
4101 // If currently creating a route, do not show this rollover window
4102 if (m_routeState) showRouteRollover = false;
4103
4104 // Similar for AIS target rollover window
4105 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4106 showRouteRollover = false;
4107
4108 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4109 !showRouteRollover) {
4110 m_pRouteRolloverWin->IsActive(false);
4111 m_pRolloverRouteSeg = NULL;
4112 m_pRouteRolloverWin->Destroy();
4113 m_pRouteRolloverWin = NULL;
4114 b_need_refresh = true;
4115 } else if (m_pRouteRolloverWin && showRouteRollover) {
4116 m_pRouteRolloverWin->IsActive(true);
4117 b_need_refresh = true;
4118 }
4119
4120 // Now the Track info rollover
4121 // Show the track segment info
4122 bool showTrackRollover = false;
4123
4124 if (NULL == m_pRolloverTrackSeg) {
4125 // Get a list of all selectable sgements, and search for the first
4126 // visible segment as the rollover target.
4127
4128 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4129 SelectableItemList SelList = pSelect->FindSelectionList(
4130 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4131
4132 auto node = SelList.begin();
4133 while (node != SelList.end()) {
4134 SelectItem *pFindSel = *node;
4135
4136 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4137
4138 if (pt && pt->IsVisible()) {
4139 m_pRolloverTrackSeg = pFindSel;
4140 showTrackRollover = true;
4141
4142 if (NULL == m_pTrackRolloverWin) {
4143 m_pTrackRolloverWin = new RolloverWin(this, 10);
4144 m_pTrackRolloverWin->IsActive(false);
4145 }
4146
4147 if (!m_pTrackRolloverWin->IsActive()) {
4148 wxString s;
4149 TrackPoint *segShow_point_a =
4150 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4151 TrackPoint *segShow_point_b =
4152 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4153
4154 double brg, dist;
4155 DistanceBearingMercator(
4156 segShow_point_b->m_lat, segShow_point_b->m_lon,
4157 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4158
4159 if (!pt->m_bIsInLayer)
4160 s.Append(_("Track") + ": ");
4161 else
4162 s.Append(_("Layer Track: "));
4163
4164 if (pt->GetName().IsEmpty())
4165 s.Append(_("(unnamed)"));
4166 else
4167 s.Append(pt->GetName());
4168 double tlenght = pt->Length();
4169 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4170 if (pt->GetLastPoint()->GetTimeString() &&
4171 pt->GetPoint(0)->GetTimeString()) {
4172 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4173 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4174 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4175 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4176 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4177 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4178 << getUsrSpeedUnit();
4179 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4180 : ttime.Format(" %H:%M"));
4181 }
4182 }
4183
4184 if (g_bShowTrackPointTime &&
4185 strlen(segShow_point_b->GetTimeString())) {
4186 wxString stamp = segShow_point_b->GetTimeString();
4187 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4188 if (timestamp.IsValid()) {
4189 // Format track rollover timestamp to OCPN global TZ setting
4192 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4193 }
4194 s << "\n" << _("Segment Created: ") << stamp;
4195 }
4196
4197 s << "\n";
4198 if (g_bShowTrue)
4199 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4200 0x00B0);
4201
4202 if (g_bShowMag) {
4203 double latAverage =
4204 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4205 double lonAverage =
4206 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4207 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4208
4209 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4210 0x00B0);
4211 }
4212
4213 s << FormatDistanceAdaptive(dist);
4214
4215 if (segShow_point_a->GetTimeString() &&
4216 segShow_point_b->GetTimeString()) {
4217 wxDateTime apoint = segShow_point_a->GetCreateTime();
4218 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4219 if (apoint.IsValid() && bpoint.IsValid()) {
4220 double segmentSpeed = toUsrSpeed(
4221 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4222 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4223 << getUsrSpeedUnit();
4224 }
4225 }
4226
4227 m_pTrackRolloverWin->SetString(s);
4228
4229 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4230 LEG_ROLLOVER, win_size);
4231 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4232 m_pTrackRolloverWin->IsActive(true);
4233 b_need_refresh = true;
4234 showTrackRollover = true;
4235 break;
4236 }
4237 } else {
4238 ++node;
4239 }
4240 }
4241 } else {
4242 // Is the cursor still in select radius, and not timed out?
4243 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4244 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4245 m_pRolloverTrackSeg))
4246 showTrackRollover = false;
4247 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4248 showTrackRollover = false;
4249 else
4250 showTrackRollover = true;
4251 }
4252
4253 // Similar for AIS target rollover window
4254 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4255 showTrackRollover = false;
4256
4257 // Similar for route rollover window
4258 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4259 showTrackRollover = false;
4260
4261 // TODO We onlt show tracks on primary canvas....
4262 // if(!IsPrimaryCanvas())
4263 // showTrackRollover = false;
4264
4265 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4266 !showTrackRollover) {
4267 m_pTrackRolloverWin->IsActive(false);
4268 m_pRolloverTrackSeg = NULL;
4269 m_pTrackRolloverWin->Destroy();
4270 m_pTrackRolloverWin = NULL;
4271 b_need_refresh = true;
4272 } else if (m_pTrackRolloverWin && showTrackRollover) {
4273 m_pTrackRolloverWin->IsActive(true);
4274 b_need_refresh = true;
4275 }
4276
4277 if (b_need_refresh) Refresh();
4278}
4279
4280void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4281 if ((GetShowENCLights() || m_bsectors_shown) &&
4282 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4283 extendedSectorLegs)) {
4284 if (!m_bsectors_shown) {
4285 ReloadVP(false);
4286 m_bsectors_shown = true;
4287 }
4288 } else {
4289 if (m_bsectors_shown) {
4290 ReloadVP(false);
4291 m_bsectors_shown = false;
4292 }
4293 }
4294
4295// This is here because GTK status window update is expensive..
4296// cairo using pango rebuilds the font every time so is very
4297// inefficient
4298// Anyway, only update the status bar when this timer expires
4299#if defined(__WXGTK__) || defined(__WXQT__)
4300 {
4301 // Check the absolute range of the cursor position
4302 // There could be a window wherein the chart geoereferencing is not
4303 // valid....
4304 double cursor_lat, cursor_lon;
4305 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4306
4307 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4308 while (cursor_lon < -180.) cursor_lon += 360.;
4309
4310 while (cursor_lon > 180.) cursor_lon -= 360.;
4311
4312 SetCursorStatus(cursor_lat, cursor_lon);
4313 }
4314 }
4315#endif
4316}
4317
4318void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4319 if (!parent_frame->m_pStatusBar) return;
4320
4321 wxString s1;
4322 s1 += " ";
4323 s1 += toSDMM(1, cursor_lat);
4324 s1 += " ";
4325 s1 += toSDMM(2, cursor_lon);
4326
4327 if (STAT_FIELD_CURSOR_LL >= 0)
4328 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4329
4330 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4331
4332 double brg, dist;
4333 wxString sm;
4334 wxString st;
4335 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4336 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4337 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4338
4339 wxString s = st + sm;
4340 s << FormatDistanceAdaptive(dist);
4341
4342 // CUSTOMIZATION - LIVE ETA OPTION
4343 // -------------------------------------------------------
4344 // Calculate an "live" ETA based on route starting from the current
4345 // position of the boat and goes to the cursor of the mouse.
4346 // In any case, an standard ETA will be calculated with a default speed
4347 // of the boat to give an estimation of the route (in particular if GPS
4348 // is off).
4349
4350 // Display only if option "live ETA" is selected in Settings > Display >
4351 // General.
4352 if (g_bShowLiveETA) {
4353 float realTimeETA;
4354 float boatSpeed;
4355 float boatSpeedDefault = g_defaultBoatSpeed;
4356
4357 // Calculate Estimate Time to Arrival (ETA) in minutes
4358 // Check before is value not closed to zero (it will make an very big
4359 // number...)
4360 if (!std::isnan(gSog)) {
4361 boatSpeed = gSog;
4362 if (boatSpeed < 0.5) {
4363 realTimeETA = 0;
4364 } else {
4365 realTimeETA = dist / boatSpeed * 60;
4366 }
4367 } else {
4368 realTimeETA = 0;
4369 }
4370
4371 // Add space after distance display
4372 s << " ";
4373 // Display ETA
4374 s << minutesToHoursDays(realTimeETA);
4375
4376 // In any case, display also an ETA with default speed at 6knts
4377
4378 s << " [@";
4379 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4380 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4381 s << " ";
4382 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4383 s << "]";
4384 }
4385 // END OF - LIVE ETA OPTION
4386
4387 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4388}
4389
4390// CUSTOMIZATION - FORMAT MINUTES
4391// -------------------------------------------------------
4392// New function to format minutes into a more readable format:
4393// * Hours + minutes, or
4394// * Days + hours.
4395wxString minutesToHoursDays(float timeInMinutes) {
4396 wxString s;
4397
4398 if (timeInMinutes == 0) {
4399 s << "--min";
4400 }
4401
4402 // Less than 60min, keep time in minutes
4403 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4404 s << wxString::Format("%d", (int)timeInMinutes);
4405 s << "min";
4406 }
4407
4408 // Between 1h and less than 24h, display time in hours, minutes
4409 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4410 int hours;
4411 int min;
4412 hours = (int)timeInMinutes / 60;
4413 min = (int)timeInMinutes % 60;
4414
4415 if (min == 0) {
4416 s << wxString::Format("%d", hours);
4417 s << "h";
4418 } else {
4419 s << wxString::Format("%d", hours);
4420 s << "h";
4421 s << wxString::Format("%d", min);
4422 s << "min";
4423 }
4424
4425 }
4426
4427 // More than 24h, display time in days, hours
4428 else if (timeInMinutes > 24 * 60) {
4429 int days;
4430 int hours;
4431 days = (int)(timeInMinutes / 60) / 24;
4432 hours = (int)(timeInMinutes / 60) % 24;
4433
4434 if (hours == 0) {
4435 s << wxString::Format("%d", days);
4436 s << "d";
4437 } else {
4438 s << wxString::Format("%d", days);
4439 s << "d";
4440 s << wxString::Format("%d", hours);
4441 s << "h";
4442 }
4443 }
4444
4445 return s;
4446}
4447
4448// END OF CUSTOMIZATION - FORMAT MINUTES
4449// Thanks open source code ;-)
4450// -------------------------------------------------------
4451
4452void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4453 double clat, clon;
4454 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4455 *lat = clat;
4456 *lon = clon;
4457}
4458
4459void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4460 wxPoint2DDouble *r) {
4461 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4462}
4463
4465 double rlon, wxPoint2DDouble *r) {
4466 // If the Current Chart is a raster chart, and the
4467 // requested lat/long is within the boundaries of the chart,
4468 // and the VP is not rotated,
4469 // then use the embedded BSB chart georeferencing algorithm
4470 // for greater accuracy
4471 // Additionally, use chart embedded georef if the projection is TMERC
4472 // i.e. NOT MERCATOR and NOT POLYCONIC
4473
4474 // If for some reason the chart rejects the request by returning an error,
4475 // then fall back to Viewport Projection estimate from canvas parameters
4476 if (!g_bopengl && m_singleChart &&
4477 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4478 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4479 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4480 (m_singleChart->GetChartProjectionType() !=
4481 PROJECTION_TRANSVERSE_MERCATOR) &&
4482 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4483 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4484 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4485 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4486 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4487 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4488 // Cur_BSB_Ch->GetCOVRTablenPoints
4489 // ( 0 ), rlon,
4490 // rlat );
4491 // bInside = true;
4492 // if ( bInside )
4493 if (Cur_BSB_Ch) {
4494 // This is a Raster chart....
4495 // If the VP is changing, the raster chart parameters may not yet be
4496 // setup So do that before accessing the chart's embedded
4497 // georeferencing
4498 Cur_BSB_Ch->SetVPRasterParms(vp);
4499 double rpixxd, rpixyd;
4500 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4501 r->m_x = rpixxd;
4502 r->m_y = rpixyd;
4503 return;
4504 }
4505 }
4506 }
4507
4508 // if needed, use the VPoint scaling estimator,
4509 *r = vp.GetDoublePixFromLL(rlat, rlon);
4510}
4511
4512// This routine might be deleted and all of the rendering improved
4513// to have floating point accuracy
4514bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4515 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4516}
4517
4518bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4519 wxPoint *r) {
4520 wxPoint2DDouble p;
4521 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4522
4523 // some projections give nan values when invisible values (other side of
4524 // world) are requested we should stop using integer coordinates or return
4525 // false here (and test it everywhere)
4526 if (std::isnan(p.m_x)) {
4527 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4528 return false;
4529 }
4530
4531 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4532 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4533 else
4534 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4535
4536 return true;
4537}
4538
4539void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4540 double &lon) {
4541 // If the Current Chart is a raster chart, and the
4542 // requested x,y is within the boundaries of the chart,
4543 // and the VP is not rotated,
4544 // then use the embedded BSB chart georeferencing algorithm
4545 // for greater accuracy
4546 // Additionally, use chart embedded georef if the projection is TMERC
4547 // i.e. NOT MERCATOR and NOT POLYCONIC
4548
4549 // If for some reason the chart rejects the request by returning an error,
4550 // then fall back to Viewport Projection estimate from canvas parameters
4551 bool bUseVP = true;
4552
4553 if (!g_bopengl && m_singleChart &&
4554 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4555 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4556 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4557 (m_singleChart->GetChartProjectionType() !=
4558 PROJECTION_TRANSVERSE_MERCATOR) &&
4559 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4560 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4561 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4562 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4563
4564 // TODO maybe need iterative process to validate bInside
4565 // first pass is mercator, then check chart boundaries
4566
4567 if (Cur_BSB_Ch) {
4568 // This is a Raster chart....
4569 // If the VP is changing, the raster chart parameters may not yet be
4570 // setup So do that before accessing the chart's embedded
4571 // georeferencing
4572 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4573
4574 double slat, slon;
4575 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4576 lat = slat;
4577
4578 if (slon < -180.)
4579 slon += 360.;
4580 else if (slon > 180.)
4581 slon -= 360.;
4582
4583 lon = slon;
4584 bUseVP = false;
4585 }
4586 }
4587 }
4588
4589 // if needed, use the VPoint scaling estimator
4590 if (bUseVP) {
4591 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4592 }
4593}
4594
4596 StopMovement();
4597 DoZoomCanvas(factor, false);
4598 extendedSectorLegs.clear();
4599}
4600
4601void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4602 bool stoptimer) {
4603 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4604
4605 if (g_bsmoothpanzoom) {
4606 if (StartTimedMovement(stoptimer)) {
4607 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4608 m_zoom_factor = factor;
4609 }
4610
4611 m_zoom_target = VPoint.chart_scale / factor;
4612 } else {
4613 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4614
4615 DoZoomCanvas(factor, can_zoom_to_cursor);
4616 }
4617
4618 extendedSectorLegs.clear();
4619}
4620
4621void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4622 // possible on startup
4623 if (!ChartData) return;
4624 if (!m_pCurrentStack) return;
4625
4626 /* TODO: queue the quilted loading code to a background thread
4627 so yield is never called from here, and also rendering is not delayed */
4628
4629 // Cannot allow Yield() re-entrancy here
4630 if (m_bzooming) return;
4631 m_bzooming = true;
4632
4633 double old_ppm = GetVP().view_scale_ppm;
4634
4635 // Capture current cursor position for zoom to cursor
4636 double zlat = m_cursor_lat;
4637 double zlon = m_cursor_lon;
4638
4639 double proposed_scale_onscreen =
4640 GetVP().chart_scale /
4641 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4642 bool b_do_zoom = false;
4643
4644 if (factor > 1) {
4645 b_do_zoom = true;
4646
4647 // double zoom_factor = factor;
4648
4649 ChartBase *pc = NULL;
4650
4651 if (!VPoint.b_quilt) {
4652 pc = m_singleChart;
4653 } else {
4654 if (!m_disable_adjust_on_zoom) {
4655 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4656 if (new_db_index >= 0)
4657 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4658 else { // for whatever reason, no reference chart is known
4659 // Choose the smallest scale chart on the current stack
4660 // and then adjust for scale range
4661 int current_ref_stack_index = -1;
4662 if (m_pCurrentStack->nEntry) {
4663 int trial_index =
4664 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4665 m_pQuilt->SetReferenceChart(trial_index);
4666 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4667 if (new_db_index >= 0)
4668 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4669 }
4670 }
4671
4672 if (m_pCurrentStack)
4673 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4674 new_db_index); // highlite the correct bar entry
4675 }
4676 }
4677
4678 if (pc) {
4679 // double target_scale_ppm = GetVPScale() * zoom_factor;
4680 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4681 // target_scale_ppm;
4682
4683 // Query the chart to determine the appropriate zoom range
4684 double min_allowed_scale =
4685 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4686
4687 if (proposed_scale_onscreen < min_allowed_scale) {
4688 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4689 m_zoom_factor = 1; /* stop zooming */
4690 b_do_zoom = false;
4691 } else
4692 proposed_scale_onscreen = min_allowed_scale;
4693 }
4694
4695 } else {
4696 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4697 }
4698
4699 } else if (factor < 1) {
4700 b_do_zoom = true;
4701
4702 ChartBase *pc = NULL;
4703
4704 bool b_smallest = false;
4705
4706 if (!VPoint.b_quilt) { // not quilted
4707 pc = m_singleChart;
4708
4709 if (pc) {
4710 // If m_singleChart is not on the screen, unbound the zoomout
4711 LLBBox viewbox = VPoint.GetBBox();
4712 // BoundingBox chart_box;
4713 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4714 double max_allowed_scale;
4715
4716 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4717
4718 // We can allow essentially unbounded zoomout in single chart mode
4719 // if( ChartData->GetDBBoundingBox( current_index,
4720 // &chart_box ) &&
4721 // !viewbox.IntersectOut( chart_box ) )
4722 // // Clamp the minimum scale zoom-out to the value
4723 // specified by the chart max_allowed_scale =
4724 // wxMin(max_allowed_scale, 4.0 *
4725 // pc->GetNormalScaleMax(
4726 // GetCanvasScaleFactor(),
4727 // GetCanvasWidth() ) );
4728 if (proposed_scale_onscreen > max_allowed_scale) {
4729 m_zoom_factor = 1; /* stop zooming */
4730 proposed_scale_onscreen = max_allowed_scale;
4731 }
4732 }
4733
4734 } else {
4735 if (!m_disable_adjust_on_zoom) {
4736 int new_db_index =
4737 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4738 if (new_db_index >= 0)
4739 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4740
4741 if (m_pCurrentStack)
4742 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4743 new_db_index); // highlite the correct bar entry
4744
4745 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4746
4747 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4748 proposed_scale_onscreen =
4749 wxMin(proposed_scale_onscreen,
4750 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4751 }
4752
4753 // set a minimum scale
4754 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4755 m_absolute_min_scale_ppm)
4756 proposed_scale_onscreen =
4757 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4758 }
4759 }
4760 double new_scale =
4761 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4762
4763 if (b_do_zoom) {
4764 // Disable ZTC if lookahead is ON, and currently b_follow is active
4765 bool b_allow_ztc = true;
4766 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4767 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4768 if (m_bLookAhead) {
4769 double brg, distance;
4770 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4771 &distance);
4772 dir_to_shift = brg;
4773 meters_to_shift = distance * 1852;
4774 }
4775 // Arrange to combine the zoom and pan into one operation for smoother
4776 // appearance
4777 SetVPScale(new_scale, false); // adjust, but deferred refresh
4778 wxPoint r;
4779 GetCanvasPointPix(zlat, zlon, &r);
4780 // this will emit the Refresh()
4781 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4782 } else {
4783 SetVPScale(new_scale);
4784 if (m_bFollow) DoCanvasUpdate();
4785 }
4786 }
4787
4788 m_bzooming = false;
4789}
4790
4791void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4792 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4793 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4794}
4795
4796int rot;
4797void ChartCanvas::RotateCanvas(double dir) {
4798 // SetUpMode(NORTH_UP_MODE);
4799
4800 if (g_bsmoothpanzoom) {
4801 if (StartTimedMovement()) {
4802 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4803 m_rotation_speed = dir * 60;
4804 }
4805 } else {
4806 double speed = dir * 10;
4807 if (m_modkeys == wxMOD_ALT) speed /= 20;
4808 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4809 }
4810}
4811
4812void ChartCanvas::DoRotateCanvas(double rotation) {
4813 while (rotation < 0) rotation += 2 * PI;
4814 while (rotation > 2 * PI) rotation -= 2 * PI;
4815
4816 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4817
4818 SetVPRotation(rotation);
4819 parent_frame->UpdateRotationState(VPoint.rotation);
4820}
4821
4822void ChartCanvas::DoTiltCanvas(double tilt) {
4823 while (tilt < 0) tilt = 0;
4824 while (tilt > .95) tilt = .95;
4825
4826 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4827
4828 VPoint.tilt = tilt;
4829 Refresh(false);
4830}
4831
4832void ChartCanvas::TogglebFollow() {
4833 if (!m_bFollow)
4834 SetbFollow();
4835 else
4836 ClearbFollow();
4837}
4838
4839void ChartCanvas::ClearbFollow() {
4840 m_bFollow = false; // update the follow flag
4841
4842 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4843
4844 UpdateFollowButtonState();
4845
4846 DoCanvasUpdate();
4847 ReloadVP();
4848 parent_frame->SetChartUpdatePeriod();
4849}
4850
4851void ChartCanvas::SetbFollow() {
4852 // Is the OWNSHIP on-screen?
4853 // If not, then reset the OWNSHIP offset to 0 (center screen)
4854 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4855 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4856 m_OSoffsetx = 0;
4857 m_OSoffsety = 0;
4858 }
4859
4860 // Apply the present b_follow offset values to ship position
4861 wxPoint2DDouble p;
4863 p.m_x += m_OSoffsetx;
4864 p.m_y -= m_OSoffsety;
4865
4866 // compute the target center screen lat/lon
4867 double dlat, dlon;
4868 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4869
4870 JumpToPosition(dlat, dlon, GetVPScale());
4871 m_bFollow = true;
4872
4873 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4874 UpdateFollowButtonState();
4875
4876 if (!g_bSmoothRecenter) {
4877 DoCanvasUpdate();
4878 ReloadVP();
4879 }
4880 parent_frame->SetChartUpdatePeriod();
4881}
4882
4883void ChartCanvas::UpdateFollowButtonState() {
4884 if (m_muiBar) {
4885 if (!m_bFollow)
4886 m_muiBar->SetFollowButtonState(0);
4887 else {
4888 if (m_bLookAhead)
4889 m_muiBar->SetFollowButtonState(2);
4890 else
4891 m_muiBar->SetFollowButtonState(1);
4892 }
4893 }
4894
4895#ifdef __ANDROID__
4896 if (!m_bFollow)
4897 androidSetFollowTool(0);
4898 else {
4899 if (m_bLookAhead)
4900 androidSetFollowTool(2);
4901 else
4902 androidSetFollowTool(1);
4903 }
4904#endif
4905
4906 // Look for plugin using API-121 or later
4907 // If found, make the follow state callback.
4908 if (g_pi_manager) {
4909 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4910 if (pic->m_enabled && pic->m_init_state) {
4911 switch (pic->m_api_version) {
4912 case 121: {
4913 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4914 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4915 break;
4916 }
4917 default:
4918 break;
4919 }
4920 }
4921 }
4922 }
4923}
4924
4925void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4926 if (g_bSmoothRecenter && !m_routeState) {
4927 if (StartSmoothJump(lat, lon, scale_ppm))
4928 return;
4929 else {
4930 // move closer to the target destination, and try again
4931 double gcDist, gcBearingEnd;
4932 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4933 &gcBearingEnd);
4934 gcBearingEnd += 180;
4935 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4936 GetCanvasWidth() / GetVPScale(); // meters
4937 double lon_offset =
4938 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4939 double new_lat = lat + (lat_offset / (1852 * 60));
4940 double new_lon = lon + (lon_offset / (1852 * 60));
4941 SetViewPoint(new_lat, new_lon);
4942 ReloadVP();
4943 StartSmoothJump(lat, lon, scale_ppm);
4944 return;
4945 }
4946 }
4947
4948 if (lon > 180.0) lon -= 360.0;
4949 m_vLat = lat;
4950 m_vLon = lon;
4951 StopMovement();
4952 m_bFollow = false;
4953
4954 if (!GetQuiltMode()) {
4955 double skew = 0;
4956 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4957 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4958 } else {
4959 if (scale_ppm != GetVPScale()) {
4960 // XXX should be done in SetViewPoint
4961 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4962 AdjustQuiltRefChart();
4963 }
4964 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4965 }
4966
4967 ReloadVP();
4968
4969 UpdateFollowButtonState();
4970
4971 // TODO
4972 // if( g_pi_manager ) {
4973 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4974 // }
4975}
4976
4977bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
4978 // Check distance to jump, in pixels at current chart scale
4979 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
4980 // width.
4981 double gcDist;
4982 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
4983 double distance_pixels = gcDist * GetVPScale();
4984 if (distance_pixels > 0.5 * GetCanvasWidth()) {
4985 // Jump is too far, try again
4986 return false;
4987 }
4988
4989 // Save where we're coming from
4990 m_startLat = m_vLat;
4991 m_startLon = m_vLon;
4992 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
4993
4994 // Save where we want to end up
4995 m_endLat = lat;
4996 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
4997 m_endScale = scale_ppm;
4998
4999 // Setup timing
5000 m_animationDuration = 600; // ms
5001 m_animationStart = wxGetLocalTimeMillis();
5002
5003 // Stop any previous movement, ensure no conflicts
5004 StopMovement();
5005 m_bFollow = false;
5006
5007 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5008 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5009 m_animationActive = true;
5010
5011 return true;
5012}
5013
5014void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5015 // Calculate time fraction from 0..1
5016 wxLongLong now = wxGetLocalTimeMillis();
5017 double elapsed = (now - m_animationStart).ToDouble();
5018 double t = elapsed / m_animationDuration.ToDouble();
5019 if (t > 1.0) t = 1.0;
5020
5021 // Ease function for smoother movement
5022 double e = easeOutCubic(t);
5023
5024 // Interpolate lat/lon/scale
5025 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5026 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5027 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5028
5029 // Update viewpoint
5030 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5031 // portion)
5032 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5033 ReloadVP();
5034
5035 // If we reached the end, stop the timer and finalize
5036 if (t >= 1.0) {
5037 m_easeTimer.Stop();
5038 m_animationActive = false;
5039 UpdateFollowButtonState();
5040 ZoomCanvasSimple(1.0001);
5041 DoCanvasUpdate();
5042 ReloadVP();
5043 }
5044}
5045
5046bool ChartCanvas::PanCanvas(double dx, double dy) {
5047 if (!ChartData) return false;
5048 extendedSectorLegs.clear();
5049
5050 double dlat, dlon;
5051 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5052
5053 int iters = 0;
5054 for (;;) {
5055 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5056
5057 if (iters++ > 5) return false;
5058 if (!std::isnan(dlat)) break;
5059
5060 dx *= .5, dy *= .5;
5061 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5062 }
5063
5064 // avoid overshooting the poles
5065 if (dlat > 90)
5066 dlat = 90;
5067 else if (dlat < -90)
5068 dlat = -90;
5069
5070 if (dlon > 360.) dlon -= 360.;
5071 if (dlon < -360.) dlon += 360.;
5072
5073 // This should not really be necessary, but round-trip georef on some
5074 // charts is not perfect, So we can get creep on repeated unidimensional
5075 // pans, and corrupt chart cacheing.......
5076
5077 // But this only works on north-up projections
5078 // TODO: can we remove this now?
5079 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5080 // .001 ) ) {
5081 //
5082 // if( dx == 0 ) dlon = clon;
5083 // if( dy == 0 ) dlat = clat;
5084 // }
5085
5086 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5087
5088 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5089
5090 if (VPoint.b_quilt) {
5091 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5092 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5093 // Tweak the scale slightly for a new ref chart
5094 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5095 if (pc) {
5096 double tweak_scale_ppm =
5097 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5098 SetVPScale(tweak_scale_ppm);
5099 }
5100 }
5101
5102 if (new_ref_dbIndex == -1) {
5103#pragma GCC diagnostic push
5104#pragma GCC diagnostic ignored "-Warray-bounds"
5105 // The compiler sees a -1 index being used. Does not happen, though.
5106
5107 // for whatever reason, no reference chart is known
5108 // Probably panned out of the coverage region
5109 // If any charts are anywhere on-screen, choose the smallest
5110 // scale chart on the screen to be a new reference chart.
5111 int trial_index = -1;
5112 if (m_pCurrentStack->nEntry) {
5113 int trial_index =
5114 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5115 }
5116
5117 if (trial_index < 0) {
5118 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5119 if (full_screen_array.size())
5120 trial_index = full_screen_array[full_screen_array.size() - 1];
5121 }
5122
5123 if (trial_index >= 0) {
5124 m_pQuilt->SetReferenceChart(trial_index);
5125 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5126 VPoint.rotation);
5127 ReloadVP();
5128 }
5129#pragma GCC diagnostic pop
5130 }
5131 }
5132
5133 // Turn off bFollow only if the ownship has left the screen
5134 if (m_bFollow) {
5135 double offx, offy;
5136 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5137
5138 double offset_angle = atan2(offy, offx);
5139 double offset_distance = sqrt((offy * offy) + (offx * offx));
5140 double chart_angle = GetVPRotation();
5141 double target_angle = chart_angle - offset_angle;
5142 double d_east_mod = offset_distance * cos(target_angle);
5143 double d_north_mod = offset_distance * sin(target_angle);
5144
5145 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5146 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5147
5148 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5149 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5150 m_bFollow = false; // update the follow flag
5151 UpdateFollowButtonState();
5152 }
5153 }
5154
5155 Refresh(false);
5156
5157 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5158
5159 return true;
5160}
5161
5162bool ChartCanvas::IsOwnshipOnScreen() {
5163 wxPoint r;
5165 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5166 ((r.y > 0) && r.y < GetCanvasHeight()))
5167 return true;
5168 else
5169 return false;
5170}
5171
5172void ChartCanvas::ReloadVP(bool b_adjust) {
5173 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5174
5175 LoadVP(VPoint, b_adjust);
5176}
5177
5178void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5179#ifdef ocpnUSE_GL
5180 if (g_bopengl && m_glcc) {
5181 m_glcc->Invalidate();
5182 if (m_glcc->GetSize() != GetSize()) {
5183 m_glcc->SetSize(GetSize());
5184 }
5185 } else
5186#endif
5187 {
5188 m_cache_vp.Invalidate();
5189 m_bm_cache_vp.Invalidate();
5190 }
5191
5192 VPoint.Invalidate();
5193
5194 if (m_pQuilt) m_pQuilt->Invalidate();
5195
5196 // Make sure that the Selected Group is sensible...
5197 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5198 // m_groupIndex = 0;
5199 // if( !CheckGroup( m_groupIndex ) )
5200 // m_groupIndex = 0;
5201
5202 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5203 vp.m_projection_type, b_adjust);
5204}
5205
5206void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5207 m_pQuilt->SetReferenceChart(dbIndex);
5208 VPoint.Invalidate();
5209 m_pQuilt->Invalidate();
5210}
5211
5212double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5213 if (m_pQuilt)
5214 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5215 else
5216 return vp.view_scale_ppm;
5217}
5218
5219// Verify and adjust the current reference chart,
5220// so that it will not lead to excessive overzoom or underzoom onscreen
5221int ChartCanvas::AdjustQuiltRefChart() {
5222 int ret = -1;
5223 if (m_pQuilt) {
5224 wxASSERT(ChartData);
5225 ChartBase *pc =
5226 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5227 if (pc) {
5228 double min_ref_scale =
5229 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5230 double max_ref_scale =
5231 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5232
5233 if (VPoint.chart_scale < min_ref_scale) {
5234 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5235 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5236 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5237 } else {
5238 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5239
5240 if (!brender_ok) {
5241 int target_stack_index = wxNOT_FOUND;
5242 int il = 0;
5243 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5244 if (index == m_pQuilt->GetRefChartdbIndex()) {
5245 target_stack_index = il;
5246 break;
5247 }
5248 il++;
5249 }
5250 if (wxNOT_FOUND == target_stack_index) // should never happen...
5251 target_stack_index = 0;
5252
5253 int ref_family = pc->GetChartFamily();
5254 int extended_array_count =
5255 m_pQuilt->GetExtendedStackIndexArray().size();
5256 while ((!brender_ok) &&
5257 ((int)target_stack_index < (extended_array_count - 1))) {
5258 target_stack_index++;
5259 int test_db_index =
5260 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5261
5262 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5263 IsChartQuiltableRef(test_db_index)) {
5264 // open the target, and check the min_scale
5265 ChartBase *ptest_chart =
5266 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5267 if (ptest_chart) {
5268 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5269 }
5270 }
5271 }
5272
5273 if (brender_ok) { // found a better reference chart
5274 int new_db_index =
5275 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5276 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5277 IsChartQuiltableRef(new_db_index)) {
5278 m_pQuilt->SetReferenceChart(new_db_index);
5279 ret = new_db_index;
5280 } else
5281 ret = m_pQuilt->GetRefChartdbIndex();
5282 } else
5283 ret = m_pQuilt->GetRefChartdbIndex();
5284
5285 } else
5286 ret = m_pQuilt->GetRefChartdbIndex();
5287 }
5288 } else
5289 ret = -1;
5290 }
5291
5292 return ret;
5293}
5294
5295void ChartCanvas::UpdateCanvasOnGroupChange() {
5296 delete m_pCurrentStack;
5297 m_pCurrentStack = new ChartStack;
5298 wxASSERT(ChartData);
5299 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5300 m_groupIndex);
5301
5302 if (m_pQuilt) {
5303 m_pQuilt->Compose(VPoint);
5304 SetFocus();
5305 }
5306}
5307
5308bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5309 double latNE, double lonNE) {
5310 // Center Point
5311 double latc = (latSW + latNE) / 2.0;
5312 double lonc = (lonSW + lonNE) / 2.0;
5313
5314 // Get scale in ppm (latitude)
5315 double ne_easting, ne_northing;
5316 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5317
5318 double sw_easting, sw_northing;
5319 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5320
5321 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5322
5323 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5324}
5325
5326bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5327 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5328 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5329}
5330
5331bool ChartCanvas::SetVPProjection(int projection) {
5332 if (!g_bopengl) // alternative projections require opengl
5333 return false;
5334
5335 // the view scale varies depending on geographic location and projection
5336 // rescale to keep the relative scale on the screen the same
5337 double prev_true_scale_ppm = m_true_scale_ppm;
5338 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5339 VPoint.skew, VPoint.rotation, projection) &&
5340 SetVPScale(wxMax(
5341 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5342 m_absolute_min_scale_ppm));
5343}
5344
5345bool ChartCanvas::SetViewPoint(double lat, double lon) {
5346 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5347 VPoint.rotation);
5348}
5349
5350bool ChartCanvas::SetVPRotation(double angle) {
5351 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5352 VPoint.skew, angle);
5353}
5354bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5355 double skew, double rotation, int projection,
5356 bool b_adjust, bool b_refresh) {
5357 bool b_ret = false;
5358 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5359 skew -= 2 * PI;
5360 // Any sensible change?
5361 if (VPoint.IsValid()) {
5362 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5363 (fabs(VPoint.skew - skew) < 1e-9) &&
5364 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5365 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5366 (VPoint.m_projection_type == projection ||
5367 projection == PROJECTION_UNKNOWN))
5368 return false;
5369 }
5370 if (VPoint.m_projection_type != projection)
5371 VPoint.InvalidateTransformCache(); // invalidate
5372
5373 // Take a local copy of the last viewport
5374 ViewPort last_vp = VPoint;
5375
5376 VPoint.skew = skew;
5377 VPoint.clat = lat;
5378 VPoint.clon = lon;
5379 VPoint.rotation = rotation;
5380 VPoint.view_scale_ppm = scale_ppm;
5381 if (projection != PROJECTION_UNKNOWN)
5382 VPoint.SetProjectionType(projection);
5383 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5384 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5385
5386 // don't allow latitude above 88 for mercator (90 is infinity)
5387 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5388 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5389 if (VPoint.clat > 89.5)
5390 VPoint.clat = 89.5;
5391 else if (VPoint.clat < -89.5)
5392 VPoint.clat = -89.5;
5393 }
5394
5395 // don't zoom out too far for transverse mercator polyconic until we resolve
5396 // issues
5397 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5398 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5399 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5400
5401 // SetVPRotation(rotation);
5402
5403 if (!g_bopengl) // tilt is not possible without opengl
5404 VPoint.tilt = 0;
5405
5406 if ((VPoint.pix_width <= 0) ||
5407 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5408 return false;
5409
5410 bool bwasValid = VPoint.IsValid();
5411 VPoint.Validate(); // Mark this ViewPoint as OK
5412
5413 // Has the Viewport scale changed? If so, invalidate the vp
5414 if (last_vp.view_scale_ppm != scale_ppm) {
5415 m_cache_vp.Invalidate();
5416 InvalidateGL();
5417 }
5418
5419 // A preliminary value, may be tweaked below
5420 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5421
5422 // recompute cursor position
5423 // and send to interested plugins if the mouse is actually in this window
5424 int mouseX = mouse_x;
5425 int mouseY = mouse_y;
5426 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5427 (mouseY < VPoint.pix_height)) {
5428 double lat, lon;
5429 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5430 m_cursor_lat = lat;
5431 m_cursor_lon = lon;
5432 SendCursorLatLonToAllPlugIns(lat, lon);
5433 }
5434
5435 if (!VPoint.b_quilt && m_singleChart) {
5436 VPoint.SetBoxes();
5437
5438 // Allow the chart to adjust the new ViewPort for performance optimization
5439 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5440 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5441
5442 // If there is a sensible change in the chart render, refresh the whole
5443 // screen
5444 if ((!m_cache_vp.IsValid()) ||
5445 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5446 Refresh(false);
5447 b_ret = true;
5448 } else {
5449 wxPoint cp_last, cp_this;
5450 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5451 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5452
5453 if (cp_last != cp_this) {
5454 Refresh(false);
5455 b_ret = true;
5456 }
5457 }
5458 // Create the stack
5459 if (m_pCurrentStack) {
5460 assert(ChartData != 0);
5461 int current_db_index;
5462 current_db_index =
5463 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5464
5465 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5466 m_groupIndex);
5467 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5468 }
5469
5470 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5471 }
5472
5473 // Handle the quilted case
5474 if (VPoint.b_quilt) {
5475 VPoint.SetBoxes();
5476
5477 if (last_vp.view_scale_ppm != scale_ppm)
5478 m_pQuilt->InvalidateAllQuiltPatchs();
5479
5480 // Create the quilt
5481 if (ChartData /*&& ChartData->IsValid()*/) {
5482 if (!m_pCurrentStack) return false;
5483
5484 int current_db_index;
5485 current_db_index =
5486 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5487
5488 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5489 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5490
5491 // Check to see if the current quilt reference chart is in the new stack
5492 int current_ref_stack_index = -1;
5493 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5494 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5495 current_ref_stack_index = i;
5496 }
5497
5498 if (g_bFullScreenQuilt) {
5499 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5500 }
5501
5502 // We might need a new Reference Chart
5503 bool b_needNewRef = false;
5504
5505 // If the new stack does not contain the current ref chart....
5506 if ((-1 == current_ref_stack_index) &&
5507 (m_pQuilt->GetRefChartdbIndex() >= 0))
5508 b_needNewRef = true;
5509
5510 // Would the current Ref Chart be excessively underzoomed?
5511 // We need to check this here to be sure, since we cannot know where the
5512 // reference chart was assigned. For instance, the reference chart may
5513 // have been selected from the config file, or from a long jump with a
5514 // chart family switch implicit. Anyway, we check to be sure....
5515 bool renderable = true;
5516 ChartBase *referenceChart =
5517 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5518 if (referenceChart) {
5519 double chartMaxScale = referenceChart->GetNormalScaleMax(
5520 GetCanvasScaleFactor(), GetCanvasWidth());
5521 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5522 }
5523 if (!renderable) b_needNewRef = true;
5524
5525 // Need new refchart?
5526 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5527 const ChartTableEntry &cte_ref =
5528 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5529 int target_scale = cte_ref.GetScale();
5530 int target_type = cte_ref.GetChartType();
5531 int candidate_stack_index;
5532
5533 // reset the ref chart in a way that does not lead to excessive
5534 // underzoom, for performance reasons Try to find a chart that is the
5535 // same type, and has a scale of just smaller than the current ref
5536 // chart
5537
5538 candidate_stack_index = 0;
5539 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5540 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5541 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5542 int candidate_scale = cte_candidate.GetScale();
5543 int candidate_type = cte_candidate.GetChartType();
5544
5545 if ((candidate_scale >= target_scale) &&
5546 (candidate_type == target_type)) {
5547 bool renderable = true;
5548 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5549 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5550 if (tentative_referenceChart) {
5551 double chartMaxScale =
5552 tentative_referenceChart->GetNormalScaleMax(
5553 GetCanvasScaleFactor(), GetCanvasWidth());
5554 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5555 }
5556
5557 if (renderable) break;
5558 }
5559
5560 candidate_stack_index++;
5561 }
5562
5563 // If that did not work, look for a chart of just larger scale and
5564 // same type
5565 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5566 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5567 while (candidate_stack_index >= 0) {
5568 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5569 if (idx >= 0) {
5570 const ChartTableEntry &cte_candidate =
5571 ChartData->GetChartTableEntry(idx);
5572 int candidate_scale = cte_candidate.GetScale();
5573 int candidate_type = cte_candidate.GetChartType();
5574
5575 if ((candidate_scale <= target_scale) &&
5576 (candidate_type == target_type))
5577 break;
5578 }
5579 candidate_stack_index--;
5580 }
5581 }
5582
5583 // and if that did not work, chose stack entry 0
5584 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5585 (candidate_stack_index < 0))
5586 candidate_stack_index = 0;
5587
5588 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5589
5590 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5591 }
5592
5593 if (!g_bopengl) {
5594 // Preset the VPoint projection type to match what the quilt projection
5595 // type will be
5596 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5597
5598 // Always keep the default Mercator projection if the reference chart is
5599 // not in the PatchList or the scale is too small for it to render.
5600
5601 bool renderable = true;
5602 ChartBase *referenceChart =
5603 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5604 if (referenceChart) {
5605 double chartMaxScale = referenceChart->GetNormalScaleMax(
5606 GetCanvasScaleFactor(), GetCanvasWidth());
5607 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5608 proj = ChartData->GetDBChartProj(ref_db_index);
5609 } else
5610 proj = PROJECTION_MERCATOR;
5611
5612 VPoint.b_MercatorProjectionOverride =
5613 (m_pQuilt->GetnCharts() == 0 || !renderable);
5614
5615 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5616
5617 VPoint.SetProjectionType(proj);
5618 }
5619
5620 // If this quilt will be a perceptible delta from the existing quilt,
5621 // then refresh the entire screen
5622 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5623 // Allow the quilt to adjust the new ViewPort for performance
5624 // optimization This will normally be only a fractional (i.e.
5625 // sub-pixel) adjustment...
5626 if (b_adjust) {
5627 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5628 }
5629
5630 // ChartData->ClearCacheInUseFlags();
5631 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5632
5633 // wxStopWatch sw;
5634
5635#ifdef __ANDROID__
5636 // This is an optimization for panning on touch screen systems.
5637 // The quilt composition is deferred until the OnPaint() message gets
5638 // finally removed and processed from the message queue.
5639 // Takes advantage of the fact that touch-screen pan gestures are
5640 // usually short in distance,
5641 // so not requiring a full quilt rebuild until the pan gesture is
5642 // complete.
5643 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5644 // qDebug() << "Force compose";
5645 m_pQuilt->Compose(VPoint);
5646 } else {
5647 m_pQuilt->Invalidate();
5648 }
5649#else
5650 m_pQuilt->Compose(VPoint);
5651#endif
5652
5653 // printf("comp time %ld\n", sw.Time());
5654
5655 // If the extended chart stack has changed, invalidate any cached
5656 // render bitmap
5657 // if(m_pQuilt->GetXStackHash() != hash1) {
5658 // m_bm_cache_vp.Invalidate();
5659 // InvalidateGL();
5660 // }
5661
5662 ChartData->PurgeCacheUnusedCharts(0.7);
5663
5664 if (b_refresh) Refresh(false);
5665
5666 b_ret = true;
5667 }
5668 }
5669
5670 VPoint.skew = 0.; // Quilting supports 0 Skew
5671 } else if (!g_bopengl) {
5672 OcpnProjType projection = PROJECTION_UNKNOWN;
5673 if (m_singleChart) // viewport projection must match chart projection
5674 // without opengl
5675 projection = m_singleChart->GetChartProjectionType();
5676 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5677 VPoint.SetProjectionType(projection);
5678 }
5679
5680 // Has the Viewport projection changed? If so, invalidate the vp
5681 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5682 m_cache_vp.Invalidate();
5683 InvalidateGL();
5684 }
5685
5686 UpdateCanvasControlBar(); // Refresh the Piano
5687
5688 VPoint.chart_scale = 1.0; // fallback default value
5689
5690 if (VPoint.GetBBox().GetValid()) {
5691 // Update the viewpoint reference scale
5692 if (m_singleChart)
5693 VPoint.ref_scale = m_singleChart->GetNativeScale();
5694 else {
5695#ifdef __ANDROID__
5696 // This is an optimization for panning on touch screen systems.
5697 // See above.
5698 // Quilt might not be fully composed at this point, so for cm93
5699 // the reference scale may not be known.
5700 // In this case, do not update the VP ref_scale.
5701 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5702 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5703 }
5704#else
5705 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5706#endif
5707 }
5708
5709 // Calculate the on-screen displayed actual scale
5710 // by a simple traverse northward from the center point
5711 // of roughly one eighth of the canvas height
5712 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5713
5714 double delta_check =
5715 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5716 delta_check /= 8.;
5717
5718 double check_point = wxMin(89., VPoint.clat);
5719
5720 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5721
5722 double rhumbDist;
5723 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5724 VPoint.clon, 0, &rhumbDist);
5725
5726 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5727 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5728 // Calculate the distance between r1 and r in physical pixels.
5729 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5730 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5731
5732 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5733
5734 // A fall back in case of very high zoom-out, giving delta_y == 0
5735 // which can probably only happen with vector charts
5736 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5737
5738 // Another fallback, for highly zoomed out charts
5739 // This adjustment makes the displayed TrueScale correspond to the
5740 // same algorithm used to calculate the chart zoom-out limit for
5741 // ChartDummy.
5742 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5743
5744 if (m_true_scale_ppm)
5745 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5746 else
5747 VPoint.chart_scale = 1.0;
5748
5749 // Create a nice renderable string
5750 double round_factor = 1000.;
5751 if (VPoint.chart_scale <= 1000.)
5752 round_factor = 10.;
5753 else if (VPoint.chart_scale <= 10000.)
5754 round_factor = 100.;
5755 else if (VPoint.chart_scale <= 100000.)
5756 round_factor = 1000.;
5757
5758 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5759 double retina_coef = 1;
5760#ifdef ocpnUSE_GL
5761#ifdef __WXOSX__
5762 if (g_bopengl) {
5763 retina_coef = GetContentScaleFactor();
5764 }
5765#endif
5766#endif
5767
5768 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5769 // rounded to the nearest 10, 100 or 1000.
5770 //
5771 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5772 // true_scale_display. That does not make sense. The chart scale should be
5773 // the same as the true scale within the limits of the rounding factor.
5774 double true_scale_display =
5775 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5776 wxString text;
5777
5778 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5779
5780 if (m_displayed_scale_factor > 10.0)
5781 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5782 m_displayed_scale_factor);
5783 else if (m_displayed_scale_factor > 1.0)
5784 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5785 m_displayed_scale_factor);
5786 else if (m_displayed_scale_factor > 0.1) {
5787 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5788 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5789 } else if (m_displayed_scale_factor > 0.01) {
5790 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5791 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5792 } else {
5793 text.Printf(
5794 "%s %4.0f (---)", _("Scale"),
5795 true_scale_display); // Generally, no chart, so no chart scale factor
5796 }
5797
5798 m_scaleValue = true_scale_display;
5799 m_scaleText = text;
5800 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5801
5802 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5803 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5804 // Check to see if the text will fit in the StatusBar field...
5805 bool b_noshow = false;
5806 {
5807 int w = 0;
5808 int h;
5809 wxClientDC dc(parent_frame->GetStatusBar());
5810 if (dc.IsOk()) {
5811 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5812 dc.SetFont(*templateFont);
5813 dc.GetTextExtent(text, &w, &h);
5814
5815 // If text is too long for the allocated field, try to reduce the text
5816 // string a bit.
5817 wxRect rect;
5818 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5819 if (w && w > rect.width) {
5820 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5821 }
5822
5823 // Test again...if too big still, then give it up.
5824 dc.GetTextExtent(text, &w, &h);
5825
5826 if (w && w > rect.width) {
5827 b_noshow = true;
5828 }
5829 }
5830 }
5831
5832 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5833 }
5834 }
5835
5836 // Maintain member vLat/vLon
5837 m_vLat = VPoint.clat;
5838 m_vLon = VPoint.clon;
5839
5840 return b_ret;
5841}
5842
5843// Static Icon definitions for some symbols requiring
5844// scaling/rotation/translation Very specific wxDC draw commands are
5845// necessary to properly render these icons...See the code in
5846// ShipDraw()
5847
5848// This icon was adapted and scaled from the S52 Presentation Library
5849// version 3_03.
5850// Symbol VECGND02
5851
5852static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5853
5854// This ownship icon was adapted and scaled from the S52 Presentation
5855// Library version 3_03 Symbol OWNSHP05
5856static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5857 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5858
5859wxColour ChartCanvas::PredColor() {
5860 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5861 // visibility.
5862 if (SHIP_NORMAL == m_ownship_state)
5863 return GetGlobalColor("URED");
5864
5865 else if (SHIP_LOWACCURACY == m_ownship_state)
5866 return GetGlobalColor("YELO1");
5867
5868 return GetGlobalColor("NODTA");
5869}
5870
5871wxColour ChartCanvas::ShipColor() {
5872 // Establish ship color
5873 // It changes color based on GPS and Chart accuracy/availability
5874
5875 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5876
5877 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5878
5879 return GetGlobalColor("URED"); // default is OK
5880}
5881
5882void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5883 wxPoint2DDouble lShipMidPoint) {
5884 dc.SetPen(wxPen(PredColor(), 2));
5885
5886 if (SHIP_NORMAL == m_ownship_state)
5887 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5888 else
5889 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5890
5891 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5892 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5893
5894 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5895 lShipMidPoint.m_y);
5896 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5897 lShipMidPoint.m_y + 12);
5898}
5899
5900void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5901 wxPoint GPSOffsetPixels,
5902 wxPoint2DDouble lGPSPoint) {
5903 // if (m_animationActive) return;
5904 // Develop a uniform length for course predictor line dash length, based on
5905 // physical display size Use this reference length to size all other graphics
5906 // elements
5907 float ref_dim = m_display_size_mm / 24;
5908 ref_dim = wxMin(ref_dim, 12);
5909 ref_dim = wxMax(ref_dim, 6);
5910
5911 wxColour cPred;
5912 cPred.Set(g_cog_predictor_color);
5913 if (cPred == wxNullColour) cPred = PredColor();
5914
5915 // Establish some graphic element line widths dependent on the platform
5916 // display resolution
5917 // double nominal_line_width_pix = wxMax(1.0,
5918 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5919 // not less than 1 pixel
5920 double nominal_line_width_pix = wxMax(
5921 1.0,
5922 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5923
5924 // If the calculated value is greater than the config file spec value, then
5925 // use it.
5926 if (nominal_line_width_pix > g_cog_predictor_width)
5927 g_cog_predictor_width = nominal_line_width_pix;
5928
5929 // Calculate ownship Position Predictor
5930 wxPoint lPredPoint, lHeadPoint;
5931
5932 float pCog = std::isnan(gCog) ? 0 : gCog;
5933 float pSog = std::isnan(gSog) ? 0 : gSog;
5934
5935 double pred_lat, pred_lon;
5936 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5937 &pred_lat, &pred_lon);
5938 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5939
5940 // test to catch the case where COG/HDG line crosses the screen
5941 LLBBox box;
5942
5943 // Should we draw the Head vector?
5944 // Compare the points lHeadPoint and lPredPoint
5945 // If they differ by more than n pixels, and the head vector is valid, then
5946 // render the head vector
5947
5948 float ndelta_pix = 10.;
5949 double hdg_pred_lat, hdg_pred_lon;
5950 bool b_render_hdt = false;
5951 if (!std::isnan(gHdt)) {
5952 // Calculate ownship Heading pointer as a predictor
5953 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5954 &hdg_pred_lon);
5955 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5956 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5957 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5958 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5959 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5960 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5961 }
5962 }
5963
5964 // draw course over ground if they are longer than the ship
5965 wxPoint lShipMidPoint;
5966 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5967 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5968 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5969 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5970
5971 if (lpp >= img_height / 2) {
5972 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5973 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5974 !std::isnan(gSog)) {
5975 // COG Predictor
5976 float dash_length = ref_dim;
5977 wxDash dash_long[2];
5978 dash_long[0] =
5979 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
5980 g_cog_predictor_width); // Long dash , in mm <---------+
5981 dash_long[1] = dash_long[0] / 2.0; // Short gap
5982
5983 // On ultra-hi-res displays, do not allow the dashes to be greater than
5984 // 250, since it is defined as (char)
5985 if (dash_length > 250.) {
5986 dash_long[0] = 250. / g_cog_predictor_width;
5987 dash_long[1] = dash_long[0] / 2;
5988 }
5989
5990 wxPen ppPen2(cPred, g_cog_predictor_width,
5991 (wxPenStyle)g_cog_predictor_style);
5992 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
5993 ppPen2.SetDashes(2, dash_long);
5994 dc.SetPen(ppPen2);
5995 dc.StrokeLine(
5996 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
5997 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
5998
5999 if (g_cog_predictor_width > 1) {
6000 float line_width = g_cog_predictor_width / 3.;
6001
6002 wxDash dash_long3[2];
6003 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6004 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6005
6006 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6007 (wxPenStyle)g_cog_predictor_style);
6008 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6009 ppPen3.SetDashes(2, dash_long3);
6010 dc.SetPen(ppPen3);
6011 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6012 lGPSPoint.m_y + GPSOffsetPixels.y,
6013 lPredPoint.x + GPSOffsetPixels.x,
6014 lPredPoint.y + GPSOffsetPixels.y);
6015 }
6016
6017 if (g_cog_predictor_endmarker) {
6018 // Prepare COG predictor endpoint icon
6019 double png_pred_icon_scale_factor = .4;
6020 if (g_ShipScaleFactorExp > 1.0)
6021 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6022 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6023
6024 wxPoint icon[4];
6025
6026 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6027 (float)(lPredPoint.x - lShipMidPoint.x));
6028 cog_rad += (float)PI;
6029
6030 for (int i = 0; i < 4; i++) {
6031 int j = i * 2;
6032 double pxa = (double)(s_png_pred_icon[j]);
6033 double pya = (double)(s_png_pred_icon[j + 1]);
6034
6035 pya *= png_pred_icon_scale_factor;
6036 pxa *= png_pred_icon_scale_factor;
6037
6038 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6039 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6040
6041 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6042 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6043 }
6044
6045 // Render COG endpoint icon
6046 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6047 wxPENSTYLE_SOLID);
6048 dc.SetPen(ppPen1);
6049 dc.SetBrush(wxBrush(cPred));
6050
6051 dc.StrokePolygon(4, icon);
6052 }
6053 }
6054 }
6055
6056 // HDT Predictor
6057 if (b_render_hdt) {
6058 float hdt_dash_length = ref_dim * 0.4;
6059
6060 cPred.Set(g_ownship_HDTpredictor_color);
6061 if (cPred == wxNullColour) cPred = PredColor();
6062 float hdt_width =
6063 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6064 : g_cog_predictor_width * 0.8);
6065 wxDash dash_short[2];
6066 dash_short[0] =
6067 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6068 hdt_width); // Short dash , in mm <---------+
6069 dash_short[1] =
6070 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6071 hdt_width); // Short gap |
6072
6073 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6074 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6075 ppPen2.SetDashes(2, dash_short);
6076
6077 dc.SetPen(ppPen2);
6078 dc.StrokeLine(
6079 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6080 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6081
6082 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6083 dc.SetPen(ppPen1);
6084 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6085
6086 if (g_ownship_HDTpredictor_endmarker) {
6087 double nominal_circle_size_pixels = wxMax(
6088 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6089
6090 // Scale the circle to ChartScaleFactor, slightly softened....
6091 if (g_ShipScaleFactorExp > 1.0)
6092 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6093
6094 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6095 lHeadPoint.y + GPSOffsetPixels.y,
6096 nominal_circle_size_pixels / 2);
6097 }
6098 }
6099
6100 // Draw radar rings if activated
6101 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6102 double factor = 1.00;
6103 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6104 factor = 1 / 1.852;
6105 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6106 if (std::isnan(gSog))
6107 factor = 0.0;
6108 else
6109 factor = gSog / 60;
6110 }
6111 factor *= g_fNavAidRadarRingsStep;
6112
6113 double tlat, tlon;
6114 wxPoint r;
6115 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6116 GetCanvasPointPix(tlat, tlon, &r);
6117
6118 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6119 pow((double)(lGPSPoint.m_y - r.y), 2));
6120 int pix_radius = (int)lpp;
6121
6122 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6123
6124 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6125
6126 dc.SetPen(ppPen1);
6127 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6128
6129 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6130 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6131 }
6132}
6133
6134void ChartCanvas::ComputeShipScaleFactor(
6135 float icon_hdt, int ownShipWidth, int ownShipLength,
6136 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6137 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6138 float screenResolution = m_pix_per_mm;
6139
6140 // Calculate the true ship length in exact pixels
6141 double ship_bow_lat, ship_bow_lon;
6142 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6143 &ship_bow_lat, &ship_bow_lon);
6144 wxPoint lShipBowPoint;
6145 wxPoint2DDouble b_point =
6146 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6147 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6148
6149 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6150 powf((float)(b_point.m_y - a_point.m_y), 2));
6151
6152 // And in mm
6153 float shipLength_mm = shipLength_px / screenResolution;
6154
6155 // Set minimum ownship drawing size
6156 float ownship_min_mm = g_n_ownship_min_mm;
6157 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6158
6159 // Calculate Nautical Miles distance from midships to gps antenna
6160 float hdt_ant = icon_hdt + 180.;
6161 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6162 float dx = g_n_gps_antenna_offset_x / 1852.;
6163 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6164 {
6165 hdt_ant = icon_hdt;
6166 dy = -dy;
6167 }
6168
6169 // If the drawn ship size is going to be clamped, adjust the gps antenna
6170 // offsets
6171 if (shipLength_mm < ownship_min_mm) {
6172 dy /= shipLength_mm / ownship_min_mm;
6173 dx /= shipLength_mm / ownship_min_mm;
6174 }
6175
6176 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6177
6178 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6179 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6180 &ship_mid_lon1);
6181
6182 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6183 &lShipMidPoint);
6184
6185 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6186 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6187
6188 float scale_factor = shipLength_px / ownShipLength;
6189
6190 // Calculate a scale factor that would produce a reasonably sized icon
6191 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6192
6193 // And choose the correct one
6194 scale_factor = wxMax(scale_factor, scale_factor_min);
6195
6196 scale_factor_y = scale_factor;
6197 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6198 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6199}
6200
6201void ChartCanvas::ShipDraw(ocpnDC &dc) {
6202 if (!GetVP().IsValid()) return;
6203
6204 wxPoint GPSOffsetPixels(0, 0);
6205 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6206
6207 // COG/SOG may be undefined in NMEA data stream
6208 float pCog = std::isnan(gCog) ? 0 : gCog;
6209 float pSog = std::isnan(gSog) ? 0 : gSog;
6210
6211 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6212
6213 lShipMidPoint = lGPSPoint;
6214
6215 // Draw the icon rotated to the COG
6216 // or to the Hdt if available
6217 float icon_hdt = pCog;
6218 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6219
6220 // COG may be undefined in NMEA data stream
6221 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6222
6223 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6224 // predictor
6225 double osd_head_lat, osd_head_lon;
6226 wxPoint osd_head_point;
6227
6228 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6229 &osd_head_lon);
6230
6231 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6232
6233 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6234 (float)(osd_head_point.x - lShipMidPoint.m_x));
6235 icon_rad += (float)PI;
6236
6237 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6238
6239 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6240 // nominal size and is just barely outside the viewport ....
6241 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6242
6243 // TODO: fix to include actual size of boat that will be rendered
6244 int img_height = 0;
6245 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6246 if (GetVP().chart_scale >
6247 300000) // According to S52, this should be 50,000
6248 {
6249 ShipDrawLargeScale(dc, lShipMidPoint);
6250 img_height = 20;
6251 } else {
6252 wxImage pos_image;
6253
6254 // Substitute user ownship image if found
6255 if (m_pos_image_user)
6256 pos_image = m_pos_image_user->Copy();
6257 else if (SHIP_NORMAL == m_ownship_state)
6258 pos_image = m_pos_image_red->Copy();
6259 if (SHIP_LOWACCURACY == m_ownship_state)
6260 pos_image = m_pos_image_yellow->Copy();
6261 else if (SHIP_NORMAL != m_ownship_state)
6262 pos_image = m_pos_image_grey->Copy();
6263
6264 // Substitute user ownship image if found
6265 if (m_pos_image_user) {
6266 pos_image = m_pos_image_user->Copy();
6267
6268 if (SHIP_LOWACCURACY == m_ownship_state)
6269 pos_image = m_pos_image_user_yellow->Copy();
6270 else if (SHIP_NORMAL != m_ownship_state)
6271 pos_image = m_pos_image_user_grey->Copy();
6272 }
6273
6274 img_height = pos_image.GetHeight();
6275
6276 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6277 g_OwnShipIconType > 0) // use large ship
6278 {
6279 int ownShipWidth = 22; // Default values from s_ownship_icon
6280 int ownShipLength = 84;
6281 if (g_OwnShipIconType == 1) {
6282 ownShipWidth = pos_image.GetWidth();
6283 ownShipLength = pos_image.GetHeight();
6284 }
6285
6286 float scale_factor_x, scale_factor_y;
6287 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6288 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6289 scale_factor_x, scale_factor_y);
6290
6291 if (g_OwnShipIconType == 1) { // Scaled bitmap
6292 pos_image.Rescale(ownShipWidth * scale_factor_x,
6293 ownShipLength * scale_factor_y,
6294 wxIMAGE_QUALITY_HIGH);
6295 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6296 wxImage rot_image =
6297 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6298
6299 // Simple sharpening algorithm.....
6300 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6301 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6302 if (rot_image.GetAlpha(ip, jp) > 64)
6303 rot_image.SetAlpha(ip, jp, 255);
6304
6305 wxBitmap os_bm(rot_image);
6306
6307 int w = os_bm.GetWidth();
6308 int h = os_bm.GetHeight();
6309 img_height = h;
6310
6311 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6312 lShipMidPoint.m_y - h / 2, true);
6313
6314 // Maintain dirty box,, missing in __WXMSW__ library
6315 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6316 lShipMidPoint.m_y - h / 2);
6317 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6318 lShipMidPoint.m_y - h / 2 + h);
6319 }
6320
6321 else if (g_OwnShipIconType == 2) { // Scaled Vector
6322 wxPoint ownship_icon[10];
6323
6324 for (int i = 0; i < 10; i++) {
6325 int j = i * 2;
6326 float pxa = (float)(s_ownship_icon[j]);
6327 float pya = (float)(s_ownship_icon[j + 1]);
6328 pya *= scale_factor_y;
6329 pxa *= scale_factor_x;
6330
6331 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6332 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6333
6334 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6335 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6336 }
6337
6338 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6339 dc.SetPen(ppPen1);
6340 dc.SetBrush(wxBrush(ShipColor()));
6341
6342 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6343
6344 // draw reference point (midships) cross
6345 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6346 ownship_icon[7].y);
6347 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6348 ownship_icon[9].y);
6349 }
6350
6351 img_height = ownShipLength * scale_factor_y;
6352
6353 // Reference point, where the GPS antenna is
6354 int circle_rad = 3;
6355 if (m_pos_image_user) circle_rad = 1;
6356
6357 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6358 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6359 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6360 } else { // Fixed bitmap icon.
6361 /* non opengl, or suboptimal opengl via ocpndc: */
6362 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6363 wxImage rot_image =
6364 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6365
6366 // Simple sharpening algorithm.....
6367 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6368 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6369 if (rot_image.GetAlpha(ip, jp) > 64)
6370 rot_image.SetAlpha(ip, jp, 255);
6371
6372 wxBitmap os_bm(rot_image);
6373
6374 if (g_ShipScaleFactorExp > 1) {
6375 wxImage scaled_image = os_bm.ConvertToImage();
6376 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6377 1.0; // soften the scale factor a bit
6378 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6379 scaled_image.GetHeight() * factor,
6380 wxIMAGE_QUALITY_HIGH));
6381 }
6382 int w = os_bm.GetWidth();
6383 int h = os_bm.GetHeight();
6384 img_height = h;
6385
6386 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6387 lShipMidPoint.m_y - h / 2, true);
6388
6389 // Reference point, where the GPS antenna is
6390 int circle_rad = 3;
6391 if (m_pos_image_user) circle_rad = 1;
6392
6393 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6394 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6395 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6396
6397 // Maintain dirty box,, missing in __WXMSW__ library
6398 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6399 lShipMidPoint.m_y - h / 2);
6400 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6401 lShipMidPoint.m_y - h / 2 + h);
6402 }
6403 } // ownship draw
6404 }
6405
6406 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6407}
6408
6409/* @ChartCanvas::CalcGridSpacing
6410 **
6411 ** Calculate the major and minor spacing between the lat/lon grid
6412 **
6413 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6414 *window
6415 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6416 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6417 ** @return [void]
6418 */
6419void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6420 float &MinorSpacing) {
6421 // table for calculating the distance between the grids
6422 // [0] view_scale ppm
6423 // [1] spacing between major grid lines in degrees
6424 // [2] spacing between minor grid lines in degrees
6425 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6426 {.000001f, 45.0f, 15.0f},
6427 {.0002f, 30.0f, 10.0f},
6428 {.0003f, 10.0f, 2.0f},
6429 {.0008f, 5.0f, 1.0f},
6430 {.001f, 2.0f, 30.0f / 60.0f},
6431 {.003f, 1.0f, 20.0f / 60.0f},
6432 {.006f, 0.5f, 10.0f / 60.0f},
6433 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6434 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6435 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6436 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6437 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6438 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6439 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6440 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6441
6442 unsigned int tabi;
6443 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6444 if (view_scale_ppm < lltab[tabi][0]) break;
6445 MajorSpacing = lltab[tabi][1]; // major latitude distance
6446 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6447 return;
6448}
6449/* @ChartCanvas::CalcGridText *************************************
6450 **
6451 ** Calculates text to display at the major grid lines
6452 **
6453 ** @param [r] latlon [float] latitude or longitude of grid line
6454 ** @param [r] spacing [float] distance between two major grid lines
6455 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6456 **
6457 ** @return
6458 */
6459
6460wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6461 int deg = (int)fabs(latlon); // degrees
6462 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6463 char postfix;
6464
6465 // calculate postfix letter (NSEW)
6466 if (latlon > 0.0) {
6467 if (bPostfix) {
6468 postfix = 'N';
6469 } else {
6470 postfix = 'E';
6471 }
6472 } else if (latlon < 0.0) {
6473 if (bPostfix) {
6474 postfix = 'S';
6475 } else {
6476 postfix = 'W';
6477 }
6478 } else {
6479 postfix = ' '; // no postfix for equator and greenwich
6480 }
6481 // calculate text, display minutes only if spacing is smaller than one degree
6482
6483 wxString ret;
6484 if (spacing >= 1.0) {
6485 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6486 } else if (spacing >= (1.0 / 60.0)) {
6487 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6488 } else {
6489 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6490 }
6491
6492 return ret;
6493}
6494
6495/* @ChartCanvas::GridDraw *****************************************
6496 **
6497 ** Draws major and minor Lat/Lon Grid on the chart
6498 ** - distance between Grid-lm ines are calculated automatic
6499 ** - major grid lines will be across the whole chart window
6500 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6501 **
6502 ** @param [w] dc [wxDC&] the wx drawing context
6503 **
6504 ** @return [void]
6505 ************************************************************************/
6506void ChartCanvas::GridDraw(ocpnDC &dc) {
6507 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6508
6509 double nlat, elon, slat, wlon;
6510 float lat, lon;
6511 float dlon;
6512 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6513 wxCoord w, h;
6514 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6515 dc.SetPen(GridPen);
6516 if (!m_pgridFont) SetupGridFont();
6517 dc.SetFont(*m_pgridFont);
6518 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6519
6520 w = m_canvas_width;
6521 h = m_canvas_height;
6522
6523 GetCanvasPixPoint(0, 0, nlat,
6524 wlon); // get lat/lon of upper left point of the window
6525 GetCanvasPixPoint(w, h, slat,
6526 elon); // get lat/lon of lower right point of the window
6527 dlon =
6528 elon -
6529 wlon; // calculate how many degrees of longitude are shown in the window
6530 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6531 {
6532 dlon = dlon + 360.0;
6533 }
6534 // calculate distance between latitude grid lines
6535 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6536
6537 // calculate position of first major latitude grid line
6538 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6539
6540 // Draw Major latitude grid lines and text
6541 while (lat < nlat) {
6542 wxPoint r;
6543 wxString st =
6544 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6545 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6546 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6547 dc.DrawText(st, 0, r.y); // draw text
6548 lat = lat + gridlatMajor;
6549
6550 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6551 }
6552
6553 // calculate position of first minor latitude grid line
6554 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6555
6556 // Draw minor latitude grid lines
6557 while (lat < nlat) {
6558 wxPoint r;
6559 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6560 dc.DrawLine(0, r.y, 10, r.y, false);
6561 dc.DrawLine(w - 10, r.y, w, r.y, false);
6562 lat = lat + gridlatMinor;
6563 }
6564
6565 // calculate distance between grid lines
6566 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6567
6568 // calculate position of first major latitude grid line
6569 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6570
6571 // draw major longitude grid lines
6572 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6573 wxPoint r;
6574 wxString st = CalcGridText(lon, gridlonMajor, false);
6575 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6576 dc.DrawLine(r.x, 0, r.x, h, false);
6577 dc.DrawText(st, r.x, 0);
6578 lon = lon + gridlonMajor;
6579 if (lon > 180.0) {
6580 lon = lon - 360.0;
6581 }
6582
6583 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6584 }
6585
6586 // calculate position of first minor longitude grid line
6587 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6588 // draw minor longitude grid lines
6589 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6590 wxPoint r;
6591 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6592 dc.DrawLine(r.x, 0, r.x, 10, false);
6593 dc.DrawLine(r.x, h - 10, r.x, h, false);
6594 lon = lon + gridlonMinor;
6595 if (lon > 180.0) {
6596 lon = lon - 360.0;
6597 }
6598 }
6599}
6600
6601void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6602 if (0 ) {
6603 double blat, blon, tlat, tlon;
6604 wxPoint r;
6605
6606 int x_origin = m_bDisplayGrid ? 60 : 20;
6607 int y_origin = m_canvas_height - 50;
6608
6609 float dist;
6610 int count;
6611 wxPen pen1, pen2;
6612
6613 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6614 {
6615 dist = 10.0;
6616 count = 5;
6617 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6618 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6619 } else // Draw 1 mile scale as SCALEB10
6620 {
6621 dist = 1.0;
6622 count = 10;
6623 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6624 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6625 }
6626
6627 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6628 double rotation = -VPoint.rotation;
6629
6630 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6631 GetCanvasPointPix(tlat, tlon, &r);
6632 int l1 = (y_origin - r.y) / count;
6633
6634 for (int i = 0; i < count; i++) {
6635 int y = l1 * i;
6636 if (i & 1)
6637 dc.SetPen(pen1);
6638 else
6639 dc.SetPen(pen2);
6640
6641 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6642 }
6643 } else {
6644 double blat, blon, tlat, tlon;
6645
6646 int x_origin = 5.0 * GetPixPerMM();
6647 int chartbar_height = GetChartbarHeight();
6648 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6649 // if (style->chartStatusWindowTransparent)
6650 // chartbar_height = 0;
6651 int y_origin = m_canvas_height - chartbar_height - 5;
6652#ifdef __WXOSX__
6653 if (!g_bopengl)
6654 y_origin =
6655 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6656#endif
6657
6658 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6659 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6660
6661 double d;
6662 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6663 d /= 2;
6664
6665 int unit = g_iDistanceFormat;
6666 if (d < .5 &&
6667 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6668 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6669
6670 // nice number
6671 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6672 float places = floor(logdist), rem = logdist - places;
6673 dist = pow(10, places);
6674
6675 if (rem < .2)
6676 dist /= 5;
6677 else if (rem < .5)
6678 dist /= 2;
6679
6680 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6681 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6682 double rotation = -VPoint.rotation;
6683
6684 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6685 &tlat, &tlon);
6686 wxPoint r;
6687 GetCanvasPointPix(tlat, tlon, &r);
6688 int l1 = r.x - x_origin;
6689
6690 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6691 12); // Store this for later reference
6692
6693 dc.SetPen(pen1);
6694
6695 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6696 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6697 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6698
6699 if (!m_pgridFont) SetupGridFont();
6700 dc.SetFont(*m_pgridFont);
6701 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6702 int w, h;
6703 dc.GetTextExtent(s, &w, &h);
6704 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6705 if (g_bopengl) {
6706 w /= dpi_factor;
6707 h /= dpi_factor;
6708 }
6709 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6710 }
6711}
6712
6713void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6714 // Constants?
6715 double da_min = 2.;
6716 double da_max = 6.;
6717 double ra_min = 0.;
6718 double ra_max = 40.;
6719
6720 wxPen pen_save = dc.GetPen();
6721
6722 wxDateTime now = wxDateTime::Now();
6723
6724 dc.SetPen(pen);
6725
6726 int x0, y0, x1, y1;
6727
6728 x0 = x1 = x + radius; // Start point
6729 y0 = y1 = y;
6730 double angle = 0.;
6731 int i = 0;
6732
6733 while (angle < 360.) {
6734 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6735 angle += da;
6736
6737 if (angle > 360.) angle = 360.;
6738
6739 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6740
6741 double r;
6742 if (i & 1)
6743 r = radius + ra;
6744 else
6745 r = radius - ra;
6746
6747 x1 = (int)(x + cos(angle * PI / 180.) * r);
6748 y1 = (int)(y + sin(angle * PI / 180.) * r);
6749
6750 dc.DrawLine(x0, y0, x1, y1);
6751
6752 x0 = x1;
6753 y0 = y1;
6754
6755 i++;
6756 }
6757
6758 dc.DrawLine(x + radius, y, x1, y1); // closure
6759
6760 dc.SetPen(pen_save);
6761}
6762
6763static bool bAnchorSoundPlaying = false;
6764
6765static void onAnchorSoundFinished(void *ptr) {
6766 o_sound::g_anchorwatch_sound->UnLoad();
6767 bAnchorSoundPlaying = false;
6768}
6769
6770void ChartCanvas::AlertDraw(ocpnDC &dc) {
6771 using namespace o_sound;
6772 // Visual and audio alert for anchorwatch goes here
6773 bool play_sound = false;
6774 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6775 if (AnchorAlertOn1) {
6776 wxPoint TargetPoint;
6778 &TargetPoint);
6779 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6780 TargetPoint.y, 100);
6781 play_sound = true;
6782 }
6783 } else
6784 AnchorAlertOn1 = false;
6785
6786 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6787 if (AnchorAlertOn2) {
6788 wxPoint TargetPoint;
6790 &TargetPoint);
6791 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6792 TargetPoint.y, 100);
6793 play_sound = true;
6794 }
6795 } else
6796 AnchorAlertOn2 = false;
6797
6798 if (play_sound) {
6799 if (!bAnchorSoundPlaying) {
6800 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6801 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6802 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6803 if (g_anchorwatch_sound->IsOk()) {
6804 bAnchorSoundPlaying = true;
6805 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6806 g_anchorwatch_sound->Play();
6807 }
6808 }
6809 }
6810}
6811
6812void ChartCanvas::UpdateShips() {
6813 // Get the rectangle in the current dc which bounds the "ownship" symbol
6814
6815 wxClientDC dc(this);
6816 if (!dc.IsOk()) return;
6817
6818 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6819 if (!test_bitmap.IsOk()) return;
6820
6821 wxMemoryDC temp_dc(test_bitmap);
6822
6823 temp_dc.ResetBoundingBox();
6824 temp_dc.DestroyClippingRegion();
6825 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6826
6827 // Draw the ownship on the temp_dc
6828 ocpnDC ocpndc = ocpnDC(temp_dc);
6829 ShipDraw(ocpndc);
6830
6831 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6832 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6833 if (p) {
6834 wxPoint px;
6835 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6836 ocpndc.CalcBoundingBox(px.x, px.y);
6837 }
6838 }
6839
6840 ship_draw_rect =
6841 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6842 temp_dc.MaxY() - temp_dc.MinY());
6843
6844 wxRect own_ship_update_rect = ship_draw_rect;
6845
6846 if (!own_ship_update_rect.IsEmpty()) {
6847 // The required invalidate rectangle is the union of the last drawn
6848 // rectangle and this drawn rectangle
6849 own_ship_update_rect.Union(ship_draw_last_rect);
6850 own_ship_update_rect.Inflate(2);
6851 }
6852
6853 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6854
6855 ship_draw_last_rect = ship_draw_rect;
6856
6857 temp_dc.SelectObject(wxNullBitmap);
6858}
6859
6860void ChartCanvas::UpdateAlerts() {
6861 // Get the rectangle in the current dc which bounds the detected Alert
6862 // targets
6863
6864 // Use this dc
6865 wxClientDC dc(this);
6866
6867 // Get dc boundary
6868 int sx, sy;
6869 dc.GetSize(&sx, &sy);
6870
6871 // Need a bitmap
6872 wxBitmap test_bitmap(sx, sy, -1);
6873
6874 // Create a memory DC
6875 wxMemoryDC temp_dc;
6876 temp_dc.SelectObject(test_bitmap);
6877
6878 temp_dc.ResetBoundingBox();
6879 temp_dc.DestroyClippingRegion();
6880 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6881
6882 // Draw the Alert Targets on the temp_dc
6883 ocpnDC ocpndc = ocpnDC(temp_dc);
6884 AlertDraw(ocpndc);
6885
6886 // Retrieve the drawing extents
6887 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6888 temp_dc.MaxX() - temp_dc.MinX(),
6889 temp_dc.MaxY() - temp_dc.MinY());
6890
6891 if (!alert_rect.IsEmpty())
6892 alert_rect.Inflate(2); // clear all drawing artifacts
6893
6894 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6895 // The required invalidate rectangle is the union of the last drawn
6896 // rectangle and this drawn rectangle
6897 wxRect alert_update_rect = alert_draw_rect;
6898 alert_update_rect.Union(alert_rect);
6899
6900 // Invalidate the rectangular region
6901 RefreshRect(alert_update_rect, false);
6902 }
6903
6904 // Save this rectangle for next time
6905 alert_draw_rect = alert_rect;
6906
6907 temp_dc.SelectObject(wxNullBitmap); // clean up
6908}
6909
6910void ChartCanvas::UpdateAIS() {
6911 if (!g_pAIS) return;
6912
6913 // Get the rectangle in the current dc which bounds the detected AIS targets
6914
6915 // Use this dc
6916 wxClientDC dc(this);
6917
6918 // Get dc boundary
6919 int sx, sy;
6920 dc.GetSize(&sx, &sy);
6921
6922 wxRect ais_rect;
6923
6924 // How many targets are there?
6925
6926 // If more than "some number", it will be cheaper to refresh the entire
6927 // screen than to build update rectangles for each target.
6928 if (g_pAIS->GetTargetList().size() > 10) {
6929 ais_rect = wxRect(0, 0, sx, sy); // full screen
6930 } else {
6931 // Need a bitmap
6932 wxBitmap test_bitmap(sx, sy, -1);
6933
6934 // Create a memory DC
6935 wxMemoryDC temp_dc;
6936 temp_dc.SelectObject(test_bitmap);
6937
6938 temp_dc.ResetBoundingBox();
6939 temp_dc.DestroyClippingRegion();
6940 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6941
6942 // Draw the AIS Targets on the temp_dc
6943 ocpnDC ocpndc = ocpnDC(temp_dc);
6944 AISDraw(ocpndc, GetVP(), this);
6945 AISDrawAreaNotices(ocpndc, GetVP(), this);
6946
6947 // Retrieve the drawing extents
6948 ais_rect =
6949 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6950 temp_dc.MaxY() - temp_dc.MinY());
6951
6952 if (!ais_rect.IsEmpty())
6953 ais_rect.Inflate(2); // clear all drawing artifacts
6954
6955 temp_dc.SelectObject(wxNullBitmap); // clean up
6956 }
6957
6958 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6959 // The required invalidate rectangle is the union of the last drawn
6960 // rectangle and this drawn rectangle
6961 wxRect ais_update_rect = ais_draw_rect;
6962 ais_update_rect.Union(ais_rect);
6963
6964 // Invalidate the rectangular region
6965 RefreshRect(ais_update_rect, false);
6966 }
6967
6968 // Save this rectangle for next time
6969 ais_draw_rect = ais_rect;
6970}
6971
6972void ChartCanvas::ToggleCPAWarn() {
6973 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6974 wxString mess;
6975 if (g_bCPAWarn) {
6976 g_bTCPA_Max = true;
6977 mess = _("ON");
6978 } else {
6979 g_bTCPA_Max = false;
6980 mess = _("OFF");
6981 }
6982 // Print to status bar if available.
6983 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
6984 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
6985 } else {
6986 if (!g_AisFirstTimeUse) {
6987 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
6988 _("CPA") + " " + mess, 4, 4);
6989 }
6990 }
6991}
6992
6993void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
6994
6995void ChartCanvas::OnSize(wxSizeEvent &event) {
6996 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
6997 // GetClientSize returns the size of the canvas area in logical pixels.
6998 GetClientSize(&m_canvas_width, &m_canvas_height);
6999
7000#ifdef __WXOSX__
7001 // Support scaled HDPI displays.
7002 m_displayScale = GetContentScaleFactor();
7003#endif
7004
7005 // Convert to physical pixels.
7006 m_canvas_width *= m_displayScale;
7007 m_canvas_height *= m_displayScale;
7008
7009 // Resize the current viewport
7010 VPoint.pix_width = m_canvas_width;
7011 VPoint.pix_height = m_canvas_height;
7012 VPoint.SetPixelScale(m_displayScale);
7013
7014 // Get some canvas metrics
7015
7016 // Rescale to current value, in order to rebuild VPoint data
7017 // structures for new canvas size
7019
7020 m_absolute_min_scale_ppm =
7021 m_canvas_width /
7022 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7023
7024 // Inform the parent Frame that I am being resized...
7025 gFrame->ProcessCanvasResize();
7026
7027 // if MUIBar is active, size the bar
7028 // if(g_useMUI && !m_muiBar){ // rebuild if
7029 // necessary
7030 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7031 // m_muiBarHOSize = m_muiBar->GetSize();
7032 // }
7033
7034 if (m_muiBar) {
7035 SetMUIBarPosition();
7036 UpdateFollowButtonState();
7037 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7038 }
7039
7040 // Set up the scroll margins
7041 xr_margin = m_canvas_width * 95 / 100;
7042 xl_margin = m_canvas_width * 5 / 100;
7043 yt_margin = m_canvas_height * 5 / 100;
7044 yb_margin = m_canvas_height * 95 / 100;
7045
7046 if (m_pQuilt)
7047 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7048
7049 // Resize the scratch BM
7050 delete pscratch_bm;
7051 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7052 m_brepaint_piano = true;
7053
7054 // Resize the Route Calculation BM
7055 m_dc_route.SelectObject(wxNullBitmap);
7056 delete proute_bm;
7057 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7058 m_dc_route.SelectObject(*proute_bm);
7059
7060 // Resize the saved Bitmap
7061 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7062
7063 // Resize the working Bitmap
7064 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7065
7066 // Rescale again, to capture all the changes for new canvas size
7068
7069#ifdef ocpnUSE_GL
7070 if (/*g_bopengl &&*/ m_glcc) {
7071 // FIXME (dave) This can go away?
7072 m_glcc->OnSize(event);
7073 }
7074#endif
7075
7076 FormatPianoKeys();
7077 // Invalidate the whole window
7078 ReloadVP();
7079}
7080
7081void ChartCanvas::ProcessNewGUIScale() {
7082 // m_muiBar->Hide();
7083 delete m_muiBar;
7084 m_muiBar = 0;
7085
7086 CreateMUIBar();
7087}
7088
7089void ChartCanvas::CreateMUIBar() {
7090 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7091 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7092 m_muiBar->SetColorScheme(m_cs);
7093 m_muiBarHOSize = m_muiBar->m_size;
7094 }
7095
7096 if (m_muiBar) {
7097 // We need to update the m_bENCGroup flag, not least for the initial
7098 // creation of a MUIBar
7099 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7100
7101 SetMUIBarPosition();
7102 UpdateFollowButtonState();
7103 m_muiBar->UpdateDynamicValues();
7104 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7105 }
7106}
7107
7108void ChartCanvas::SetMUIBarPosition() {
7109 // if MUIBar is active, size the bar
7110 if (m_muiBar) {
7111 // We estimate the piano width based on the canvas width
7112 int pianoWidth = GetClientSize().x * 0.6f;
7113 // If the piano already exists, we can use its exact width
7114 // if(m_Piano)
7115 // pianoWidth = m_Piano->GetWidth();
7116
7117 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7118 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7119 delete m_muiBar;
7120 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7121 m_muiBar->SetColorScheme(m_cs);
7122 }
7123 }
7124
7125 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7126 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7127 delete m_muiBar;
7128 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7129 m_muiBar->SetColorScheme(m_cs);
7130 }
7131 }
7132
7133 m_muiBar->SetBestPosition();
7134 }
7135}
7136
7137void ChartCanvas::DestroyMuiBar() {
7138 if (m_muiBar) {
7139 delete m_muiBar;
7140 m_muiBar = NULL;
7141 }
7142}
7143
7144void ChartCanvas::ShowCompositeInfoWindow(
7145 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7146 if (n_charts > 0) {
7147 if (NULL == m_pCIWin) {
7148 m_pCIWin = new ChInfoWin(this);
7149 m_pCIWin->Hide();
7150 }
7151
7152 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7153 wxString s;
7154
7155 s = _("Composite of ");
7156
7157 wxString s1;
7158 s1.Printf("%d ", n_charts);
7159 if (n_charts > 1)
7160 s1 += _("charts");
7161 else
7162 s1 += _("chart");
7163 s += s1;
7164 s += '\n';
7165
7166 s1.Printf(_("Chart scale"));
7167 s1 += ": ";
7168 wxString s2;
7169 s2.Printf("1:%d\n", scale);
7170 s += s1;
7171 s += s2;
7172
7173 s1 = _("Zoom in for more information");
7174 s += s1;
7175 s += '\n';
7176
7177 int char_width = s1.Length();
7178 int char_height = 3;
7179
7180 if (g_bChartBarEx) {
7181 s += '\n';
7182 int j = 0;
7183 for (int i : index_vector) {
7184 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7185 wxString path = cte.GetFullSystemPath();
7186 s += path;
7187 s += '\n';
7188 char_height++;
7189 char_width = wxMax(char_width, path.Length());
7190 if (j++ >= 9) break;
7191 }
7192 if (j >= 9) {
7193 s += " .\n .\n .\n";
7194 char_height += 3;
7195 }
7196 s += '\n';
7197 char_height += 1;
7198
7199 char_width += 4; // Fluff
7200 }
7201
7202 m_pCIWin->SetString(s);
7203
7204 m_pCIWin->FitToChars(char_width, char_height);
7205
7206 wxPoint p;
7207 p.x = x / GetContentScaleFactor();
7208 if ((p.x + m_pCIWin->GetWinSize().x) >
7209 (m_canvas_width / GetContentScaleFactor()))
7210 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7211 m_pCIWin->GetWinSize().x) /
7212 2; // centered
7213
7214 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7215 4 - m_pCIWin->GetWinSize().y;
7216
7217 m_pCIWin->dbIndex = 0;
7218 m_pCIWin->chart_scale = 0;
7219 m_pCIWin->SetPosition(p);
7220 m_pCIWin->SetBitmap();
7221 m_pCIWin->Refresh();
7222 m_pCIWin->Show();
7223 }
7224 } else {
7225 HideChartInfoWindow();
7226 }
7227}
7228
7229void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7230 if (dbIndex >= 0) {
7231 if (NULL == m_pCIWin) {
7232 m_pCIWin = new ChInfoWin(this);
7233 m_pCIWin->Hide();
7234 }
7235
7236 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7237 wxString s;
7238 ChartBase *pc = NULL;
7239
7240 // TOCTOU race but worst case will reload chart.
7241 // need to lock it or the background spooler may evict charts in
7242 // OpenChartFromDBAndLock
7243 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7244 pc = ChartData->OpenChartFromDBAndLock(
7245 dbIndex, FULL_INIT); // this must come from cache
7246
7247 int char_width, char_height;
7248 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7249 if (pc) ChartData->UnLockCacheChart(dbIndex);
7250
7251 m_pCIWin->SetString(s);
7252 m_pCIWin->FitToChars(char_width, char_height);
7253
7254 wxPoint p;
7255 p.x = x / GetContentScaleFactor();
7256 if ((p.x + m_pCIWin->GetWinSize().x) >
7257 (m_canvas_width / GetContentScaleFactor()))
7258 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7259 m_pCIWin->GetWinSize().x) /
7260 2; // centered
7261
7262 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7263 4 - m_pCIWin->GetWinSize().y;
7264
7265 m_pCIWin->dbIndex = dbIndex;
7266 m_pCIWin->SetPosition(p);
7267 m_pCIWin->SetBitmap();
7268 m_pCIWin->Refresh();
7269 m_pCIWin->Show();
7270 }
7271 } else {
7272 HideChartInfoWindow();
7273 }
7274}
7275
7276void ChartCanvas::HideChartInfoWindow() {
7277 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7278 m_pCIWin->Hide();
7279 m_pCIWin->Destroy();
7280 m_pCIWin = NULL;
7281
7282#ifdef __ANDROID__
7283 androidForceFullRepaint();
7284#endif
7285 }
7286}
7287
7288void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7289 wxMouseEvent ev(wxEVT_MOTION);
7290 ev.m_x = mouse_x;
7291 ev.m_y = mouse_y;
7292 ev.m_leftDown = mouse_leftisdown;
7293
7294 wxEvtHandler *evthp = GetEventHandler();
7295
7296 ::wxPostEvent(evthp, ev);
7297}
7298
7299void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7300 if ((m_panx_target_final - m_panx_target_now) ||
7301 (m_pany_target_final - m_pany_target_now)) {
7302 DoTimedMovementTarget();
7303 } else
7304 DoTimedMovement();
7305}
7306
7307void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7308
7309bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7310 int delta) {
7311 if (m_disable_edge_pan) return false;
7312
7313 bool bft = false;
7314 int pan_margin = m_canvas_width * margin / 100;
7315 int pan_timer_set = 200;
7316 double pan_delta = GetVP().pix_width * delta / 100;
7317 int pan_x = 0;
7318 int pan_y = 0;
7319
7320 if (x > m_canvas_width - pan_margin) {
7321 bft = true;
7322 pan_x = pan_delta;
7323 }
7324
7325 else if (x < pan_margin) {
7326 bft = true;
7327 pan_x = -pan_delta;
7328 }
7329
7330 if (y < pan_margin) {
7331 bft = true;
7332 pan_y = -pan_delta;
7333 }
7334
7335 else if (y > m_canvas_height - pan_margin) {
7336 bft = true;
7337 pan_y = pan_delta;
7338 }
7339
7340 // Of course, if dragging, and the mouse left button is not down, we must
7341 // stop the event injection
7342 if (bdragging) {
7343 if (!g_btouch) {
7344 wxMouseState state = ::wxGetMouseState();
7345#if wxCHECK_VERSION(3, 0, 0)
7346 if (!state.LeftIsDown())
7347#else
7348 if (!state.LeftDown())
7349#endif
7350 bft = false;
7351 }
7352 }
7353 if ((bft) && !pPanTimer->IsRunning()) {
7354 PanCanvas(pan_x, pan_y);
7355 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7356 return true;
7357 }
7358
7359 // This mouse event must not be due to pan timer event injector
7360 // Mouse is out of the pan zone, so prevent any orphan event injection
7361 if ((!bft) && pPanTimer->IsRunning()) {
7362 pPanTimer->Stop();
7363 }
7364
7365 return (false);
7366}
7367
7368// Look for waypoints at the current position.
7369// Used to determine what a mouse event should act on.
7370
7371void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7372 bool setBeingEdited) {
7373 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7374 m_pRoutePointEditTarget = NULL;
7375 m_pFoundPoint = NULL;
7376
7377 SelectItem *pFind = NULL;
7378 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7379 SelectableItemList SelList = pSelect->FindSelectionList(
7380 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7381 for (SelectItem *pFind : SelList) {
7382 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7383
7384 // Get an array of all routes using this point
7385 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7386 // TODO: delete m_pEditRouteArray after use?
7387
7388 // Use route array to determine actual visibility for the point
7389 bool brp_viz = false;
7390 if (m_pEditRouteArray) {
7391 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7392 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7393 if (pr->IsVisible()) {
7394 brp_viz = true;
7395 break;
7396 }
7397 }
7398 } else
7399 brp_viz = frp->IsVisible(); // isolated point
7400
7401 if (brp_viz) {
7402 // Use route array to rubberband all affected routes
7403 if (m_pEditRouteArray) // Editing Waypoint as part of route
7404 {
7405 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7406 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7407 pr->m_bIsBeingEdited = setBeingEdited;
7408 }
7409 m_bRouteEditing = setBeingEdited;
7410 } else // editing Mark
7411 {
7412 frp->m_bRPIsBeingEdited = setBeingEdited;
7413 m_bMarkEditing = setBeingEdited;
7414 }
7415
7416 m_pRoutePointEditTarget = frp;
7417 m_pFoundPoint = pFind;
7418 break; // out of the while(node)
7419 }
7420 } // for (SelectItem...
7421}
7422std::shared_ptr<PI_PointContext> ChartCanvas::GetCanvasContextAtPoint(int x,
7423 int y) {
7424 // General Right Click
7425 // Look for selectable objects
7426 double slat, slon;
7427 GetCanvasPixPoint(x, y, slat, slon);
7428
7429 SelectItem *pFindAIS;
7430 SelectItem *pFindRP;
7431 SelectItem *pFindRouteSeg;
7432 SelectItem *pFindTrackSeg;
7433 SelectItem *pFindCurrent = NULL;
7434 SelectItem *pFindTide = NULL;
7435
7436 // Get all the selectable things at the selected point
7437 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7438 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7439 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7440 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7441 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7442
7443 if (m_bShowCurrent)
7444 pFindCurrent =
7445 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7446
7447 if (m_bShowTide) // look for tide stations
7448 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7449
7450 int seltype = 0;
7451
7452 // Try for AIS targets first
7453 int FoundAIS_MMSI = 0;
7454 if (pFindAIS) {
7455 FoundAIS_MMSI = pFindAIS->GetUserData();
7456
7457 // Make sure the target data is available
7458 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7459 seltype |= SELTYPE_AISTARGET;
7460 }
7461
7462 // Now the various Route Parts
7463
7464 RoutePoint *FoundRoutePoint = NULL;
7465 Route *SelectedRoute = NULL;
7466
7467 if (pFindRP) {
7468 RoutePoint *pFirstVizPoint = NULL;
7469 RoutePoint *pFoundActiveRoutePoint = NULL;
7470 RoutePoint *pFoundVizRoutePoint = NULL;
7471 Route *pSelectedActiveRoute = NULL;
7472 Route *pSelectedVizRoute = NULL;
7473
7474 // There is at least one routepoint, so get the whole list
7475 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7476 SelectableItemList SelList =
7477 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7478 for (SelectItem *pFindSel : SelList) {
7479 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7480
7481 // Get an array of all routes using this point
7482 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7483
7484 // Use route array (if any) to determine actual visibility for this point
7485 bool brp_viz = false;
7486 if (proute_array) {
7487 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7488 Route *pr = (Route *)proute_array->Item(ir);
7489 if (pr->IsVisible()) {
7490 brp_viz = true;
7491 break;
7492 }
7493 }
7494 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7495 // but still exists as a waypoint
7496 brp_viz = prp->IsVisible(); // so treat as isolated point
7497
7498 } else
7499 brp_viz = prp->IsVisible(); // isolated point
7500
7501 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7502
7503 // Use route array to choose the appropriate route
7504 // Give preference to any active route, otherwise select the first visible
7505 // route in the array for this point
7506 if (proute_array) {
7507 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7508 Route *pr = (Route *)proute_array->Item(ir);
7509 if (pr->m_bRtIsActive) {
7510 pSelectedActiveRoute = pr;
7511 pFoundActiveRoutePoint = prp;
7512 break;
7513 }
7514 }
7515
7516 if (NULL == pSelectedVizRoute) {
7517 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7518 Route *pr = (Route *)proute_array->Item(ir);
7519 if (pr->IsVisible()) {
7520 pSelectedVizRoute = pr;
7521 pFoundVizRoutePoint = prp;
7522 break;
7523 }
7524 }
7525 }
7526
7527 delete proute_array;
7528 }
7529 }
7530
7531 // Now choose the "best" selections
7532 if (pFoundActiveRoutePoint) {
7533 FoundRoutePoint = pFoundActiveRoutePoint;
7534 SelectedRoute = pSelectedActiveRoute;
7535 } else if (pFoundVizRoutePoint) {
7536 FoundRoutePoint = pFoundVizRoutePoint;
7537 SelectedRoute = pSelectedVizRoute;
7538 } else
7539 // default is first visible point in list
7540 FoundRoutePoint = pFirstVizPoint;
7541
7542 if (SelectedRoute) {
7543 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7544 } else if (FoundRoutePoint) {
7545 seltype |= SELTYPE_MARKPOINT;
7546 }
7547
7548 // Highlight the selected point, to verify the proper right click selection
7549#if 0
7550 if (m_pFoundRoutePoint) {
7551 m_pFoundRoutePoint->m_bPtIsSelected = true;
7552 wxRect wp_rect;
7553 RoutePointGui(*m_pFoundRoutePoint)
7554 .CalculateDCRect(m_dc_route, this, &wp_rect);
7555 RefreshRect(wp_rect, true);
7556 }
7557#endif
7558 }
7559
7560 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7561 // routes But call the popup handler with identifier appropriate to the type
7562 if (pFindRouteSeg) // there is at least one select item
7563 {
7564 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7565 SelectableItemList SelList =
7566 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7567
7568 if (NULL == SelectedRoute) // the case where a segment only is selected
7569 {
7570 // Choose the first visible route containing segment in the list
7571 for (SelectItem *pFindSel : SelList) {
7572 Route *pr = (Route *)pFindSel->m_pData3;
7573 if (pr->IsVisible()) {
7574 SelectedRoute = pr;
7575 break;
7576 }
7577 }
7578 }
7579
7580 if (SelectedRoute) {
7581 if (NULL == FoundRoutePoint)
7582 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7583
7584 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7585 seltype |= SELTYPE_ROUTESEGMENT;
7586 }
7587 }
7588
7589#if 0
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 wxSelectableItemListNode *node = SelList.GetFirst();
7598 while (node) {
7599 SelectItem *pFindSel = node->GetData();
7600
7601 Track *pt = (Track *)pFindSel->m_pData3;
7602 if (pt->IsVisible()) {
7603 m_pSelectedTrack = pt;
7604 break;
7605 }
7606 node = node->GetNext();
7607 }
7608
7609 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7610 }
7611#endif
7612
7613 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7614
7615 // Populate the return struct
7616 auto rstruct = std::make_shared<PI_PointContext>();
7617 rstruct->object_type = OBJECT_CHART;
7618 rstruct->object_ident = "";
7619
7620 if (seltype == SELTYPE_AISTARGET) {
7621 rstruct->object_type = OBJECT_AISTARGET;
7622 wxString val;
7623 val.Printf("%d", FoundAIS_MMSI);
7624 rstruct->object_ident = val.ToStdString();
7625 } else if (seltype & SELTYPE_MARKPOINT) {
7626 if (FoundRoutePoint) {
7627 rstruct->object_type = OBJECT_ROUTEPOINT;
7628 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7629 }
7630 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7631 if (SelectedRoute) {
7632 rstruct->object_type = OBJECT_ROUTESEGMENT;
7633 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7634 }
7635 }
7636
7637 return rstruct;
7638}
7639
7640void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7641 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7642 singleClickEventIsValid = false;
7643 m_DoubleClickTimer->Stop();
7644}
7645
7646bool leftIsDown;
7647
7648bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7649 if (!m_bChartDragging && !m_bDrawingRoute) {
7650 /*
7651 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7652 * mouse event coordinates are in logical pixels.
7653 */
7654 if (m_Compass && m_Compass->IsShown()) {
7655 wxRect logicalRect = m_Compass->GetLogicalRect();
7656 bool isInCompass = logicalRect.Contains(event.GetPosition());
7657 if (isInCompass || m_mouseWasInCompass) {
7658 if (m_Compass->MouseEvent(event)) {
7659 cursor_region = CENTER;
7660 if (!g_btouch) SetCanvasCursor(event);
7661 m_mouseWasInCompass = isInCompass;
7662 return true;
7663 }
7664 }
7665 m_mouseWasInCompass = isInCompass;
7666 }
7667
7668 if (m_notification_button && m_notification_button->IsShown()) {
7669 wxRect logicalRect = m_notification_button->GetLogicalRect();
7670 bool isinButton = logicalRect.Contains(event.GetPosition());
7671 if (isinButton) {
7672 SetCursor(*pCursorArrow);
7673 if (event.LeftDown()) HandleNotificationMouseClick();
7674 return true;
7675 }
7676 }
7677
7678 if (MouseEventToolbar(event)) return true;
7679
7680 if (MouseEventChartBar(event)) return true;
7681
7682 if (MouseEventMUIBar(event)) return true;
7683
7684 if (MouseEventIENCBar(event)) return true;
7685 }
7686 return false;
7687}
7688
7689void ChartCanvas::HandleNotificationMouseClick() {
7690 if (!m_NotificationsList) {
7691 m_NotificationsList = new NotificationsList(this);
7692
7693 // calculate best size for Notification list
7694 m_NotificationsList->RecalculateSize();
7695 m_NotificationsList->Hide();
7696 }
7697
7698 if (m_NotificationsList->IsShown()) {
7699 m_NotificationsList->Hide();
7700 } else {
7701 m_NotificationsList->RecalculateSize();
7702 m_NotificationsList->ReloadNotificationList();
7703 m_NotificationsList->Show();
7704 }
7705}
7706bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7707 if (!g_bShowChartBar) return false;
7708
7709 if (!m_Piano->MouseEvent(event)) return false;
7710
7711 cursor_region = CENTER;
7712 if (!g_btouch) SetCanvasCursor(event);
7713 return true;
7714}
7715
7716bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7717 if (!IsPrimaryCanvas()) return false;
7718
7719 if (g_MainToolbar) {
7720 if (!g_MainToolbar->MouseEvent(event))
7721 return false;
7722 else
7723 g_MainToolbar->RefreshToolbar();
7724 }
7725
7726 cursor_region = CENTER;
7727 if (!g_btouch) SetCanvasCursor(event);
7728 return true;
7729}
7730
7731bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7732 if (!IsPrimaryCanvas()) return false;
7733
7734 if (g_iENCToolbar) {
7735 if (!g_iENCToolbar->MouseEvent(event))
7736 return false;
7737 else {
7738 g_iENCToolbar->RefreshToolbar();
7739 return true;
7740 }
7741 }
7742 return false;
7743}
7744
7745bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7746 if (m_muiBar) {
7747 if (!m_muiBar->MouseEvent(event)) return false;
7748 }
7749
7750 cursor_region = CENTER;
7751 if (!g_btouch) SetCanvasCursor(event);
7752 if (m_muiBar)
7753 return true;
7754 else
7755 return false;
7756}
7757
7758bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7759 int x, y;
7760
7761 bool bret = false;
7762
7763 event.GetPosition(&x, &y);
7764
7765 x *= m_displayScale;
7766 y *= m_displayScale;
7767
7768 m_MouseDragging = event.Dragging();
7769
7770 // Some systems produce null drag events, where the pointer position has not
7771 // changed from the previous value. Detect this case, and abort further
7772 // processing (FS#1748)
7773#ifdef __WXMSW__
7774 if (event.Dragging()) {
7775 if ((x == mouse_x) && (y == mouse_y)) return true;
7776 }
7777#endif
7778
7779 mouse_x = x;
7780 mouse_y = y;
7781 mouse_leftisdown = event.LeftDown();
7783
7784 // Establish the event region
7785 cursor_region = CENTER;
7786
7787 int chartbar_height = GetChartbarHeight();
7788
7789 if (m_Compass && m_Compass->IsShown() &&
7790 m_Compass->GetRect().Contains(event.GetPosition())) {
7791 cursor_region = CENTER;
7792 } else if (x > xr_margin) {
7793 cursor_region = MID_RIGHT;
7794 } else if (x < xl_margin) {
7795 cursor_region = MID_LEFT;
7796 } else if (y > yb_margin - chartbar_height &&
7797 y < m_canvas_height - chartbar_height) {
7798 cursor_region = MID_TOP;
7799 } else if (y < yt_margin) {
7800 cursor_region = MID_BOT;
7801 } else {
7802 cursor_region = CENTER;
7803 }
7804
7805 if (!g_btouch) SetCanvasCursor(event);
7806
7807 // Protect from leftUp's coming from event handlers in child
7808 // windows who return focus to the canvas.
7809 leftIsDown = event.LeftDown();
7810
7811#ifndef __WXOSX__
7812 if (event.LeftDown()) {
7813 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7814 // The menu bar is temporarily visible due to alt having been pressed.
7815 // Clicking will hide it, and do nothing else.
7816 g_bTempShowMenuBar = false;
7817 parent_frame->ApplyGlobalSettings(false);
7818 return (true);
7819 }
7820 }
7821#endif
7822
7823 // Update modifiers here; some window managers never send the key event
7824 m_modkeys = 0;
7825 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7826 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7827
7828#ifdef __WXMSW__
7829 // TODO Test carefully in other platforms, remove ifdef....
7830 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7831 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7832#endif
7833
7834 event.SetEventObject(this);
7835 if (SendMouseEventToPlugins(event))
7836 return (true); // PlugIn did something, and does not want the canvas to
7837 // do anything else
7838
7839 // Capture LeftUp's and time them, unless it already came from the timer.
7840
7841 // Detect end of chart dragging
7842 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7843 StartChartDragInertia();
7844 }
7845
7846 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7847 !singleClickEventIsValid) {
7848 // Ignore the second LeftUp after the DClick.
7849 if (m_DoubleClickTimer->IsRunning()) {
7850 m_DoubleClickTimer->Stop();
7851 return (true);
7852 }
7853
7854 // Save the event for later running if there is no DClick.
7855 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7856 singleClickEvent = event;
7857 singleClickEventIsValid = true;
7858 return (true);
7859 }
7860
7861 // This logic is necessary on MSW to handle the case where
7862 // a context (right-click) menu is dismissed without action
7863 // by clicking on the chart surface.
7864 // We need to avoid an unintentional pan by eating some clicks...
7865#ifdef __WXMSW__
7866 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7867 if (g_click_stop > 0) {
7868 g_click_stop--;
7869 return (true);
7870 }
7871 }
7872#endif
7873
7874 // Kick off the Rotation control timer
7875 if (GetUpMode() == COURSE_UP_MODE) {
7876 m_b_rot_hidef = false;
7877 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7878 } else
7879 pRotDefTimer->Stop();
7880
7881 // Retrigger the route leg / AIS target popup timer
7882 bool bRoll = !g_btouch;
7883#ifdef __ANDROID__
7884 bRoll = g_bRollover;
7885#endif
7886 if (bRoll) {
7887 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7888 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7889 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7890 m_RolloverPopupTimer.Start(
7891 10,
7892 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7893 else
7894 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7895 }
7896
7897 // Retrigger the cursor tracking timer
7898 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7899
7900// Show cursor position on Status Bar, if present
7901// except for GTK, under which status bar updates are very slow
7902// due to Update() call.
7903// In this case, as a workaround, update the status window
7904// after an interval timer (pCurTrackTimer) pops, which will happen
7905// whenever the mouse has stopped moving for specified interval.
7906// See the method OnCursorTrackTimerEvent()
7907#if !defined(__WXGTK__) && !defined(__WXQT__)
7908 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7909#endif
7910
7911 // Send the current cursor lat/lon to all PlugIns requesting it
7912 if (g_pi_manager) {
7913 // Occasionally, MSW will produce nonsense events on right click....
7914 // This results in an error in cursor geo position, so we skip this case
7915 if ((x >= 0) && (y >= 0))
7916 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7917 }
7918
7919 if (!g_btouch) {
7920 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7921 wxPoint p = ClientToScreen(wxPoint(x, y));
7922 }
7923 }
7924
7925 if (1 ) {
7926 // Route Creation Rubber Banding
7927 if (m_routeState >= 2) {
7928 r_rband.x = x;
7929 r_rband.y = y;
7930 m_bDrawingRoute = true;
7931
7932 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7933 Refresh(false);
7934 }
7935
7936 // Measure Tool Rubber Banding
7937 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7938 r_rband.x = x;
7939 r_rband.y = y;
7940 m_bDrawingRoute = true;
7941
7942 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7943 Refresh(false);
7944 }
7945 }
7946 return bret;
7947}
7948
7949int ChartCanvas::PrepareContextSelections(double lat, double lon) {
7950 // On general Right Click
7951 // Look for selectable objects
7952 double slat = lat;
7953 double slon = lon;
7954
7955#if defined(__WXMAC__) || defined(__ANDROID__)
7956 wxScreenDC sdc;
7957 ocpnDC dc(sdc);
7958#else
7959 wxClientDC cdc(GetParent());
7960 ocpnDC dc(cdc);
7961#endif
7962
7963 SelectItem *pFindAIS;
7964 SelectItem *pFindRP;
7965 SelectItem *pFindRouteSeg;
7966 SelectItem *pFindTrackSeg;
7967 SelectItem *pFindCurrent = NULL;
7968 SelectItem *pFindTide = NULL;
7969
7970 // Deselect any current objects
7971 if (m_pSelectedRoute) {
7972 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7973 m_pSelectedRoute->DeSelectRoute();
7974#ifdef ocpnUSE_GL
7975 if (g_bopengl && m_glcc) {
7976 InvalidateGL();
7977 Update();
7978 } else
7979#endif
7980 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7981 }
7982
7983 if (m_pFoundRoutePoint) {
7984 m_pFoundRoutePoint->m_bPtIsSelected = false;
7985 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
7986 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
7987 }
7988
7991 if (g_btouch && m_pRoutePointEditTarget) {
7992 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7993 m_pRoutePointEditTarget->m_bPtIsSelected = false;
7994 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7995 }
7996
7997 // Get all the selectable things at the cursor
7998 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7999 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8000 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8001 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8002 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8003
8004 if (m_bShowCurrent)
8005 pFindCurrent =
8006 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8007
8008 if (m_bShowTide) // look for tide stations
8009 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8010
8011 int seltype = 0;
8012
8013 // Try for AIS targets first
8014 if (pFindAIS) {
8015 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8016
8017 // Make sure the target data is available
8018 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8019 seltype |= SELTYPE_AISTARGET;
8020 }
8021
8022 // Now examine the various Route parts
8023
8024 m_pFoundRoutePoint = NULL;
8025 if (pFindRP) {
8026 RoutePoint *pFirstVizPoint = NULL;
8027 RoutePoint *pFoundActiveRoutePoint = NULL;
8028 RoutePoint *pFoundVizRoutePoint = NULL;
8029 Route *pSelectedActiveRoute = NULL;
8030 Route *pSelectedVizRoute = NULL;
8031
8032 // There is at least one routepoint, so get the whole list
8033 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8034 SelectableItemList SelList =
8035 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8036 for (SelectItem *pFindSel : SelList) {
8037 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8038
8039 // Get an array of all routes using this point
8040 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8041
8042 // Use route array (if any) to determine actual visibility for this point
8043 bool brp_viz = false;
8044 if (proute_array) {
8045 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8046 Route *pr = (Route *)proute_array->Item(ir);
8047 if (pr->IsVisible()) {
8048 brp_viz = true;
8049 break;
8050 }
8051 }
8052 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8053 // but still exists as a waypoint
8054 brp_viz = prp->IsVisible(); // so treat as isolated point
8055
8056 } else
8057 brp_viz = prp->IsVisible(); // isolated point
8058
8059 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8060
8061 // Use route array to choose the appropriate route
8062 // Give preference to any active route, otherwise select the first visible
8063 // route in the array for this point
8064 m_pSelectedRoute = NULL;
8065 if (proute_array) {
8066 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8067 Route *pr = (Route *)proute_array->Item(ir);
8068 if (pr->m_bRtIsActive) {
8069 pSelectedActiveRoute = pr;
8070 pFoundActiveRoutePoint = prp;
8071 break;
8072 }
8073 }
8074
8075 if (NULL == pSelectedVizRoute) {
8076 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8077 Route *pr = (Route *)proute_array->Item(ir);
8078 if (pr->IsVisible()) {
8079 pSelectedVizRoute = pr;
8080 pFoundVizRoutePoint = prp;
8081 break;
8082 }
8083 }
8084 }
8085
8086 delete proute_array;
8087 }
8088 }
8089
8090 // Now choose the "best" selections
8091 if (pFoundActiveRoutePoint) {
8092 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8093 m_pSelectedRoute = pSelectedActiveRoute;
8094 } else if (pFoundVizRoutePoint) {
8095 m_pFoundRoutePoint = pFoundVizRoutePoint;
8096 m_pSelectedRoute = pSelectedVizRoute;
8097 } else
8098 // default is first visible point in list
8099 m_pFoundRoutePoint = pFirstVizPoint;
8100
8101 if (m_pSelectedRoute) {
8102 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8103 } else if (m_pFoundRoutePoint) {
8104 seltype |= SELTYPE_MARKPOINT;
8105 }
8106
8107 // Highlight the selected point, to verify the proper right click selection
8108 if (m_pFoundRoutePoint) {
8109 m_pFoundRoutePoint->m_bPtIsSelected = true;
8110 wxRect wp_rect;
8111 RoutePointGui(*m_pFoundRoutePoint)
8112 .CalculateDCRect(m_dc_route, this, &wp_rect);
8113 RefreshRect(wp_rect, true);
8114 }
8115 }
8116
8117 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8118 // routes But call the popup handler with identifier appropriate to the type
8119 if (pFindRouteSeg) // there is at least one select item
8120 {
8121 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8122 SelectableItemList SelList =
8123 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8124
8125 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8126 {
8127 // Choose the first visible route containing segment in the list
8128 for (SelectItem *pFindSel : SelList) {
8129 Route *pr = (Route *)pFindSel->m_pData3;
8130 if (pr->IsVisible()) {
8131 m_pSelectedRoute = pr;
8132 break;
8133 }
8134 }
8135 }
8136
8137 if (m_pSelectedRoute) {
8138 if (NULL == m_pFoundRoutePoint)
8139 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8140
8141 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8142 if (m_pSelectedRoute->m_bRtIsSelected) {
8143#ifdef ocpnUSE_GL
8144 if (g_bopengl && m_glcc) {
8145 InvalidateGL();
8146 Update();
8147 } else
8148#endif
8149 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8150 }
8151 seltype |= SELTYPE_ROUTESEGMENT;
8152 }
8153 }
8154
8155 if (pFindTrackSeg) {
8156 m_pSelectedTrack = NULL;
8157 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8158 SelectableItemList SelList =
8159 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8160
8161 // Choose the first visible track containing segment in the list
8162 for (SelectItem *pFindSel : SelList) {
8163 Track *pt = (Track *)pFindSel->m_pData3;
8164 if (pt->IsVisible()) {
8165 m_pSelectedTrack = pt;
8166 break;
8167 }
8168 }
8169 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8170 }
8171
8172#if 0 // disable tide and current graph on right click
8173 {
8174 if (pFindCurrent) {
8175 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8176 seltype |= SELTYPE_CURRENTPOINT;
8177 }
8178
8179 else if (pFindTide) {
8180 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8181 seltype |= SELTYPE_TIDEPOINT;
8182 }
8183 }
8184#endif
8185
8186 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8187
8188 return seltype;
8189}
8190
8191IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8192 // There may be multiple current entries at the same point.
8193 // For example, there often is a current substation (with directions
8194 // specified) co-located with its master. We want to select the
8195 // substation, so that the direction will be properly indicated on the
8196 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8197 // substation)
8198 IDX_entry *pIDX_best_candidate;
8199
8200 SelectItem *pFind = NULL;
8201 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8202 SelectableItemList SelList =
8203 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8204
8205 // Default is first entry
8206 pFind = *SelList.begin();
8207 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8208
8209 auto node = SelList.begin();
8210 if (SelList.size() > 1) {
8211 for (++node; node != SelList.end(); ++node) {
8212 pFind = *node;
8213 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8214 if (pIDX_candidate->IDX_type == 'c') {
8215 pIDX_best_candidate = pIDX_candidate;
8216 break;
8217 }
8218 } // while (node)
8219 } else {
8220 pFind = *SelList.begin();
8221 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8222 }
8223
8224 return pIDX_best_candidate;
8225}
8226void ChartCanvas::CallPopupMenu(int x, int y) {
8227 last_drag.x = x;
8228 last_drag.y = y;
8229 if (m_routeState) { // creating route?
8230 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8231 return;
8232 }
8233
8235
8236 // If tide or current point is selected, then show the TC dialog immediately
8237 // without context menu
8238 if (SELTYPE_CURRENTPOINT == seltype) {
8239 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8240 Refresh(false);
8241 return;
8242 }
8243
8244 if (SELTYPE_TIDEPOINT == seltype) {
8245 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8246 Refresh(false);
8247 return;
8248 }
8249
8250 InvokeCanvasMenu(x, y, seltype);
8251
8252 // Clean up if not deleted in InvokeCanvasMenu
8253 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8254 m_pSelectedRoute->m_bRtIsSelected = false;
8255 }
8256
8257 m_pSelectedRoute = NULL;
8258
8259 if (m_pFoundRoutePoint) {
8260 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8261 m_pFoundRoutePoint->m_bPtIsSelected = false;
8262 }
8263 m_pFoundRoutePoint = NULL;
8264
8265 Refresh(true);
8266 // Refresh(false); // needed for MSW, not GTK Why??
8267}
8268
8269bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8270 // For now just bail out completely if the point clicked is not on the chart
8271 if (std::isnan(m_cursor_lat)) return false;
8272
8273 // Mouse Clicks
8274 bool ret = false; // return true if processed
8275
8276 int x, y, mx, my;
8277 event.GetPosition(&x, &y);
8278 mx = x;
8279 my = y;
8280
8281 // Calculate meaningful SelectRadius
8282 float SelectRadius;
8283 SelectRadius = g_Platform->GetSelectRadiusPix() /
8284 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8285
8287 // We start with Double Click processing. The first left click just starts a
8288 // timer and is remembered, then we actually do something if there is a
8289 // LeftDClick. If there is, the two single clicks are ignored.
8290
8291 if (event.LeftDClick() && (cursor_region == CENTER)) {
8292 m_DoubleClickTimer->Start();
8293 singleClickEventIsValid = false;
8294
8295 double zlat, zlon;
8297 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8298
8299 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8300 if (m_bShowAIS) {
8301 SelectItem *pFindAIS;
8302 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8303
8304 if (pFindAIS) {
8305 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8306 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8307 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8308 }
8309 return true;
8310 }
8311 }
8312
8313 SelectableItemList rpSelList =
8314 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8315 bool b_onRPtarget = false;
8316 for (SelectItem *pFind : rpSelList) {
8317 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8318 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8319 b_onRPtarget = true;
8320 break;
8321 }
8322 }
8323
8324 // Double tap with selected RoutePoint or Mark
8325
8326 if (m_pRoutePointEditTarget) {
8327 if (b_onRPtarget) {
8328 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8329 return true;
8330 } else {
8331 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8332 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8333 if (g_btouch)
8334 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8335 wxRect wp_rect;
8336 RoutePointGui(*m_pRoutePointEditTarget)
8337 .CalculateDCRect(m_dc_route, this, &wp_rect);
8338 m_pRoutePointEditTarget = NULL; // cancel selection
8339 RefreshRect(wp_rect, true);
8340 return true;
8341 }
8342 } else {
8343 auto node = rpSelList.begin();
8344 if (node != rpSelList.end()) {
8345 SelectItem *pFind = *node;
8346 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8347 if (frp) {
8348 wxArrayPtrVoid *proute_array =
8350
8351 // Use route array (if any) to determine actual visibility for this
8352 // point
8353 bool brp_viz = false;
8354 if (proute_array) {
8355 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8356 Route *pr = (Route *)proute_array->Item(ir);
8357 if (pr->IsVisible()) {
8358 brp_viz = true;
8359 break;
8360 }
8361 }
8362 delete proute_array;
8363 if (!brp_viz &&
8364 frp->IsShared()) // is not visible as part of route, but still
8365 // exists as a waypoint
8366 brp_viz = frp->IsVisible(); // so treat as isolated point
8367 } else
8368 brp_viz = frp->IsVisible(); // isolated point
8369
8370 if (brp_viz) {
8371 ShowMarkPropertiesDialog(frp);
8372 return true;
8373 }
8374 }
8375 }
8376 }
8377
8378 SelectItem *cursorItem;
8379 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8380
8381 if (cursorItem) {
8382 Route *pr = (Route *)cursorItem->m_pData3;
8383 if (pr->IsVisible()) {
8384 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8385 return true;
8386 }
8387 }
8388
8389 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8390
8391 if (cursorItem) {
8392 Track *pt = (Track *)cursorItem->m_pData3;
8393 if (pt->IsVisible()) {
8394 ShowTrackPropertiesDialog(pt);
8395 return true;
8396 }
8397 }
8398
8399 // Tide and current points
8400 SelectItem *pFindCurrent = NULL;
8401 SelectItem *pFindTide = NULL;
8402
8403 if (m_bShowCurrent) { // look for current stations
8404 pFindCurrent =
8405 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8406 if (pFindCurrent) {
8407 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8408 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8409 Refresh(false);
8410 return true;
8411 }
8412 }
8413
8414 if (m_bShowTide) { // look for tide stations
8415 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8416 if (pFindTide) {
8417 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8418 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8419 Refresh(false);
8420 return true;
8421 }
8422 }
8423
8424 // Found no object to act on, so show chart info.
8425 ShowObjectQueryWindow(x, y, zlat, zlon);
8426 return true;
8427 }
8428
8430 if (event.LeftDown()) {
8431 // This really should not be needed, but....
8432 // on Windows, when using wxAUIManager, sometimes the focus is lost
8433 // when clicking into another pane, e.g.the AIS target list, and then back
8434 // to this pane. Oddly, some mouse events are not lost, however. Like this
8435 // one....
8436 SetFocus();
8437
8438 last_drag.x = mx;
8439 last_drag.y = my;
8440 leftIsDown = true;
8441
8442 if (!g_btouch) {
8443 if (m_routeState) // creating route?
8444 {
8445 double rlat, rlon;
8446 bool appending = false;
8447 bool inserting = false;
8448 Route *tail = 0;
8449
8450 SetCursor(*pCursorPencil);
8451 rlat = m_cursor_lat;
8452 rlon = m_cursor_lon;
8453
8454 m_bRouteEditing = true;
8455
8456 if (m_routeState == 1) {
8457 m_pMouseRoute = new Route();
8458 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8459 pRouteList->push_back(m_pMouseRoute);
8460 r_rband.x = x;
8461 r_rband.y = y;
8462 }
8463
8464 // Check to see if there is a nearby point which may be reused
8465 RoutePoint *pMousePoint = NULL;
8466
8467 // Calculate meaningful SelectRadius
8468 double nearby_radius_meters =
8469 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8470
8471 RoutePoint *pNearbyPoint =
8472 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8473 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8474 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8475 wxArrayPtrVoid *proute_array =
8476 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8477
8478 // Use route array (if any) to determine actual visibility for this
8479 // point
8480 bool brp_viz = false;
8481 if (proute_array) {
8482 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8483 Route *pr = (Route *)proute_array->Item(ir);
8484 if (pr->IsVisible()) {
8485 brp_viz = true;
8486 break;
8487 }
8488 }
8489 delete proute_array;
8490 if (!brp_viz &&
8491 pNearbyPoint->IsShared()) // is not visible as part of route,
8492 // but still exists as a waypoint
8493 brp_viz =
8494 pNearbyPoint->IsVisible(); // so treat as isolated point
8495 } else
8496 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8497
8498 if (brp_viz) {
8499 wxString msg = _("Use nearby waypoint?");
8500 // Don't add a mark without name to the route. Name it if needed
8501 const bool noname(pNearbyPoint->GetName() == "");
8502 if (noname) {
8503 msg =
8504 _("Use nearby nameless waypoint and name it M with"
8505 " a unique number?");
8506 }
8507 // Avoid route finish on focus change for message dialog
8508 m_FinishRouteOnKillFocus = false;
8509 int dlg_return =
8510 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8511 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8512 m_FinishRouteOnKillFocus = true;
8513 if (dlg_return == wxID_YES) {
8514 if (noname) {
8515 if (m_pMouseRoute) {
8516 int last_wp_num = m_pMouseRoute->GetnPoints();
8517 // AP-ECRMB will truncate to 6 characters
8518 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8519 wxString wp_name = wxString::Format(
8520 "M%002i-%s", last_wp_num + 1, guid_short);
8521 pNearbyPoint->SetName(wp_name);
8522 } else
8523 pNearbyPoint->SetName("WPXX");
8524 }
8525 pMousePoint = pNearbyPoint;
8526
8527 // Using existing waypoint, so nothing to delete for undo.
8528 if (m_routeState > 1)
8529 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8530 Undo_HasParent, NULL);
8531
8532 tail =
8533 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8534 bool procede = false;
8535 if (tail) {
8536 procede = true;
8537 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8538 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8539 procede = false;
8540 }
8541
8542 if (procede) {
8543 int dlg_return;
8544 m_FinishRouteOnKillFocus = false;
8545 if (m_routeState ==
8546 1) { // first point in new route, preceeding route to be
8547 // added? Not touch case
8548
8549 wxString dmsg =
8550 _("Insert first part of this route in the new route?");
8551 if (tail->GetIndexOf(pMousePoint) ==
8552 tail->GetnPoints()) // Starting on last point of another
8553 // route?
8554 dmsg = _("Insert this route in the new route?");
8555
8556 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8557 dlg_return = OCPNMessageBox(
8558 this, dmsg, _("OpenCPN Route Create"),
8559 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8560 m_FinishRouteOnKillFocus = true;
8561
8562 if (dlg_return == wxID_YES) {
8563 inserting = true; // part of the other route will be
8564 // preceeding the new route
8565 }
8566 }
8567 } else {
8568 wxString dmsg =
8569 _("Append last part of this route to the new route?");
8570 if (tail->GetIndexOf(pMousePoint) == 1)
8571 dmsg = _(
8572 "Append this route to the new route?"); // Picking the
8573 // first point
8574 // of another
8575 // route?
8576
8577 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8578 dlg_return = OCPNMessageBox(
8579 this, dmsg, _("OpenCPN Route Create"),
8580 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8581 m_FinishRouteOnKillFocus = true;
8582
8583 if (dlg_return == wxID_YES) {
8584 appending = true; // part of the other route will be
8585 // appended to the new route
8586 }
8587 }
8588 }
8589 }
8590
8591 // check all other routes to see if this point appears in any
8592 // other route If it appears in NO other route, then it should e
8593 // considered an isolated mark
8594 if (!FindRouteContainingWaypoint(pMousePoint))
8595 pMousePoint->SetShared(true);
8596 }
8597 }
8598 }
8599
8600 if (NULL == pMousePoint) { // need a new point
8601 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8602 "", wxEmptyString);
8603 pMousePoint->SetNameShown(false);
8604
8605 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8606
8607 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8608
8609 if (m_routeState > 1)
8610 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8611 Undo_IsOrphanded, NULL);
8612 }
8613
8614 if (m_pMouseRoute) {
8615 if (m_routeState == 1) {
8616 // First point in the route.
8617 m_pMouseRoute->AddPoint(pMousePoint);
8618 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8619 } else {
8620 if (m_pMouseRoute->m_NextLegGreatCircle) {
8621 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8622 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8623 &rhumbBearing, &rhumbDist);
8624 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8625 rlat, &gcDist, &gcBearing, NULL);
8626 double gcDistNM = gcDist / 1852.0;
8627
8628 // Empirically found expression to get reasonable route segments.
8629 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8630 pow(rhumbDist - gcDistNM - 1, 0.5);
8631
8632 wxString msg;
8633 msg << _("For this leg the Great Circle route is ")
8634 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8635 << _(" shorter than rhumbline.\n\n")
8636 << _("Would you like include the Great Circle routing points "
8637 "for this leg?");
8638
8639 m_FinishRouteOnKillFocus = false;
8640 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8641 // does not fully capture mouse
8642
8643 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8644 wxYES_NO | wxNO_DEFAULT);
8645
8646 m_disable_edge_pan = false;
8647 m_FinishRouteOnKillFocus = true;
8648
8649 if (answer == wxID_YES) {
8650 RoutePoint *gcPoint;
8651 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8652 wxRealPoint gcCoord;
8653
8654 for (int i = 1; i <= segmentCount; i++) {
8655 double fraction = (double)i * (1.0 / (double)segmentCount);
8656 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8657 gcDist * fraction, gcBearing,
8658 &gcCoord.x, &gcCoord.y, NULL);
8659
8660 if (i < segmentCount) {
8661 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8662 wxEmptyString);
8663 gcPoint->SetNameShown(false);
8664 // pConfig->AddNewWayPoint(gcPoint, -1);
8665 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8666
8667 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8668 gcPoint);
8669 } else {
8670 gcPoint = pMousePoint; // Last point, previously exsisting!
8671 }
8672
8673 m_pMouseRoute->AddPoint(gcPoint);
8674 pSelect->AddSelectableRouteSegment(
8675 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8676 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8677 prevGcPoint = gcPoint;
8678 }
8679
8680 undo->CancelUndoableAction(true);
8681
8682 } else {
8683 m_pMouseRoute->AddPoint(pMousePoint);
8684 pSelect->AddSelectableRouteSegment(
8685 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8686 pMousePoint, m_pMouseRoute);
8687 undo->AfterUndoableAction(m_pMouseRoute);
8688 }
8689 } else {
8690 // Ordinary rhumblinesegment.
8691 m_pMouseRoute->AddPoint(pMousePoint);
8692 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8693 rlon, m_prev_pMousePoint,
8694 pMousePoint, m_pMouseRoute);
8695 undo->AfterUndoableAction(m_pMouseRoute);
8696 }
8697 }
8698 }
8699 m_prev_rlat = rlat;
8700 m_prev_rlon = rlon;
8701 m_prev_pMousePoint = pMousePoint;
8702 if (m_pMouseRoute)
8703 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8704
8705 m_routeState++;
8706
8707 if (appending ||
8708 inserting) { // Appending a route or making a new route
8709 int connect = tail->GetIndexOf(pMousePoint);
8710 if (connect == 1) {
8711 inserting = false; // there is nothing to insert
8712 appending = true; // so append
8713 }
8714 int length = tail->GetnPoints();
8715
8716 int i;
8717 int start, stop;
8718 if (appending) {
8719 start = connect + 1;
8720 stop = length;
8721 } else { // inserting
8722 start = 1;
8723 stop = connect;
8724 m_pMouseRoute->RemovePoint(
8725 m_pMouseRoute
8726 ->GetLastPoint()); // Remove the first and only point
8727 }
8728 for (i = start; i <= stop; i++) {
8729 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8730 if (m_pMouseRoute)
8731 m_pMouseRoute->m_lastMousePointIndex =
8732 m_pMouseRoute->GetnPoints();
8733 m_routeState++;
8734 gFrame->RefreshAllCanvas();
8735 ret = true;
8736 }
8737 m_prev_rlat =
8738 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8739 m_prev_rlon =
8740 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8741 m_pMouseRoute->FinalizeForRendering();
8742 }
8743 gFrame->RefreshAllCanvas();
8744 ret = true;
8745 }
8746
8747 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8748 {
8749 SetCursor(*pCursorPencil);
8750
8751 if (!m_pMeasureRoute) {
8752 m_pMeasureRoute = new Route();
8753 pRouteList->push_back(m_pMeasureRoute);
8754 }
8755
8756 if (m_nMeasureState == 1) {
8757 r_rband.x = x;
8758 r_rband.y = y;
8759 }
8760
8761 RoutePoint *pMousePoint =
8762 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8763 wxEmptyString, wxEmptyString);
8764 pMousePoint->m_bShowName = false;
8765 pMousePoint->SetShowWaypointRangeRings(false);
8766
8767 m_pMeasureRoute->AddPoint(pMousePoint);
8768
8769 m_prev_rlat = m_cursor_lat;
8770 m_prev_rlon = m_cursor_lon;
8771 m_prev_pMousePoint = pMousePoint;
8772 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8773
8774 m_nMeasureState++;
8775 gFrame->RefreshAllCanvas();
8776 ret = true;
8777 }
8778
8779 else {
8780 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8781 }
8782 } // !g_btouch
8783 else { // g_btouch
8784 m_last_touch_down_pos = event.GetPosition();
8785
8786 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8787 // if near screen edge, pan with injection
8788 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8789 // return;
8790 // }
8791 }
8792 }
8793
8794 if (ret) return true;
8795 }
8796
8797 if (event.Dragging()) {
8798 // in touch screen mode ensure the finger/cursor is on the selected point's
8799 // radius to allow dragging
8800 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8801 if (g_btouch) {
8802 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8803 SelectItem *pFind = NULL;
8804 SelectableItemList SelList = pSelect->FindSelectionList(
8805 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8806 for (SelectItem *pFind : SelList) {
8807 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8808 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8809 }
8810 }
8811
8812 // Check for use of dragHandle
8813 if (m_pRoutePointEditTarget &&
8814 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8815 SelectItem *pFind = NULL;
8816 SelectableItemList SelList = pSelect->FindSelectionList(
8817 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8818 for (SelectItem *pFind : SelList) {
8819 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8820 if (m_pRoutePointEditTarget == frp) {
8821 m_bIsInRadius = true;
8822 break;
8823 }
8824 }
8825
8826 if (!m_dragoffsetSet) {
8827 RoutePointGui(*m_pRoutePointEditTarget)
8828 .PresetDragOffset(this, mouse_x, mouse_y);
8829 m_dragoffsetSet = true;
8830 }
8831 }
8832 }
8833
8834 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8835 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8836
8837 if (NULL == g_pMarkInfoDialog) {
8838 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8839 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8840 DraggingAllowed = false;
8841
8842 if (m_pRoutePointEditTarget &&
8843 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8844 DraggingAllowed = false;
8845
8846 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8847
8848 if (DraggingAllowed) {
8849 if (!undo->InUndoableAction()) {
8850 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8851 Undo_NeedsCopy, m_pFoundPoint);
8852 }
8853
8854 // Get the update rectangle for the union of the un-edited routes
8855 wxRect pre_rect;
8856
8857 if (!g_bopengl && m_pEditRouteArray) {
8858 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8859 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8860 // Need to validate route pointer
8861 // Route may be gone due to drgging close to ownship with
8862 // "Delete On Arrival" state set, as in the case of
8863 // navigating to an isolated waypoint on a temporary route
8864 if (g_pRouteMan->IsRouteValid(pr)) {
8865 wxRect route_rect;
8866 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8867 pre_rect.Union(route_rect);
8868 }
8869 }
8870 }
8871
8872 double new_cursor_lat = m_cursor_lat;
8873 double new_cursor_lon = m_cursor_lon;
8874
8875 if (CheckEdgePan(x, y, true, 5, 2))
8876 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8877
8878 // update the point itself
8879 if (g_btouch) {
8880 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8881 // new_cursor_lat, new_cursor_lon);
8882 RoutePointGui(*m_pRoutePointEditTarget)
8883 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8884 // update the Drag Handle entry in the pSelect list
8885 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8886 m_pRoutePointEditTarget,
8887 SELTYPE_DRAGHANDLE);
8888 m_pFoundPoint->m_slat =
8889 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8890 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8891 } else {
8892 m_pRoutePointEditTarget->m_lat =
8893 new_cursor_lat; // update the RoutePoint entry
8894 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8895 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8896 m_pFoundPoint->m_slat =
8897 new_cursor_lat; // update the SelectList entry
8898 m_pFoundPoint->m_slon = new_cursor_lon;
8899 }
8900
8901 // Update the MarkProperties Dialog, if currently shown
8902 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8903 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8904 g_pMarkInfoDialog->UpdateProperties(true);
8905 }
8906
8907 if (g_bopengl) {
8908 // InvalidateGL();
8909 Refresh(false);
8910 } else {
8911 // Get the update rectangle for the edited route
8912 wxRect post_rect;
8913
8914 if (m_pEditRouteArray) {
8915 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8916 ir++) {
8917 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8918 if (g_pRouteMan->IsRouteValid(pr)) {
8919 wxRect route_rect;
8920 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8921 post_rect.Union(route_rect);
8922 }
8923 }
8924 }
8925
8926 // Invalidate the union region
8927 pre_rect.Union(post_rect);
8928 RefreshRect(pre_rect, false);
8929 }
8930 gFrame->RefreshCanvasOther(this);
8931 m_bRoutePoinDragging = true;
8932 }
8933 ret = true;
8934 } // if Route Editing
8935
8936 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8937 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8938
8939 if (NULL == g_pMarkInfoDialog) {
8940 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8941 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8942 DraggingAllowed = false;
8943
8944 if (m_pRoutePointEditTarget &&
8945 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8946 DraggingAllowed = false;
8947
8948 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8949
8950 if (DraggingAllowed) {
8951 if (!undo->InUndoableAction()) {
8952 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8953 Undo_NeedsCopy, m_pFoundPoint);
8954 }
8955
8956 // The mark may be an anchorwatch
8957 double lpp1 = 0.;
8958 double lpp2 = 0.;
8959 double lppmax;
8960
8961 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8962 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8963 }
8964 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8965 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8966 }
8967 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8968
8969 // Get the update rectangle for the un-edited mark
8970 wxRect pre_rect;
8971 if (!g_bopengl) {
8972 RoutePointGui(*m_pRoutePointEditTarget)
8973 .CalculateDCRect(m_dc_route, this, &pre_rect);
8974 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8975 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8976 (int)(lppmax - (pre_rect.height / 2)));
8977 }
8978
8979 // update the point itself
8980 if (g_btouch) {
8981 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8982 // m_cursor_lat, m_cursor_lon);
8983 RoutePointGui(*m_pRoutePointEditTarget)
8984 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8985 // update the Drag Handle entry in the pSelect list
8986 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
8987 m_pRoutePointEditTarget,
8988 SELTYPE_DRAGHANDLE);
8989 m_pFoundPoint->m_slat =
8990 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8991 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8992 } else {
8993 m_pRoutePointEditTarget->m_lat =
8994 m_cursor_lat; // update the RoutePoint entry
8995 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
8996 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8997 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
8998 m_pFoundPoint->m_slon = m_cursor_lon;
8999 }
9000
9001 // Update the MarkProperties Dialog, if currently shown
9002 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9003 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9004 g_pMarkInfoDialog->UpdateProperties(true);
9005 }
9006
9007 // Invalidate the union region
9008 if (g_bopengl) {
9009 if (!g_btouch) InvalidateGL();
9010 Refresh(false);
9011 } else {
9012 // Get the update rectangle for the edited mark
9013 wxRect post_rect;
9014 RoutePointGui(*m_pRoutePointEditTarget)
9015 .CalculateDCRect(m_dc_route, this, &post_rect);
9016 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9017 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9018 (int)(lppmax - (post_rect.height / 2)));
9019
9020 // Invalidate the union region
9021 pre_rect.Union(post_rect);
9022 RefreshRect(pre_rect, false);
9023 }
9024 gFrame->RefreshCanvasOther(this);
9025 m_bRoutePoinDragging = true;
9026 }
9027 ret = g_btouch ? m_bRoutePoinDragging : true;
9028 }
9029
9030 if (ret) return true;
9031 } // dragging
9032
9033 if (event.LeftUp()) {
9034 bool b_startedit_route = false;
9035 m_dragoffsetSet = false;
9036
9037 if (g_btouch) {
9038 m_bChartDragging = false;
9039 m_bIsInRadius = false;
9040
9041 if (m_routeState) // creating route?
9042 {
9043 if (m_bedge_pan) {
9044 m_bedge_pan = false;
9045 return false;
9046 }
9047
9048 double rlat, rlon;
9049 bool appending = false;
9050 bool inserting = false;
9051 Route *tail = 0;
9052
9053 rlat = m_cursor_lat;
9054 rlon = m_cursor_lon;
9055
9056 if (m_pRoutePointEditTarget) {
9057 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9058 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9059 if (!g_bopengl) {
9060 wxRect wp_rect;
9061 RoutePointGui(*m_pRoutePointEditTarget)
9062 .CalculateDCRect(m_dc_route, this, &wp_rect);
9063 RefreshRect(wp_rect, true);
9064 }
9065 m_pRoutePointEditTarget = NULL;
9066 }
9067 m_bRouteEditing = true;
9068
9069 if (m_routeState == 1) {
9070 m_pMouseRoute = new Route();
9071 m_pMouseRoute->SetHiLite(50);
9072 pRouteList->push_back(m_pMouseRoute);
9073 r_rband.x = x;
9074 r_rband.y = y;
9075 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9076 }
9077
9078 // Check to see if there is a nearby point which may be reused
9079 RoutePoint *pMousePoint = NULL;
9080
9081 // Calculate meaningful SelectRadius
9082 double nearby_radius_meters =
9083 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9084
9085 RoutePoint *pNearbyPoint =
9086 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9087 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9088 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9089 int dlg_return;
9090#ifndef __WXOSX__
9091 m_FinishRouteOnKillFocus =
9092 false; // Avoid route finish on focus change for message dialog
9093 dlg_return = OCPNMessageBox(
9094 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9095 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9096 m_FinishRouteOnKillFocus = true;
9097#else
9098 dlg_return = wxID_YES;
9099#endif
9100 if (dlg_return == wxID_YES) {
9101 pMousePoint = pNearbyPoint;
9102
9103 // Using existing waypoint, so nothing to delete for undo.
9104 if (m_routeState > 1)
9105 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9106 Undo_HasParent, NULL);
9107 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9108
9109 bool procede = false;
9110 if (tail) {
9111 procede = true;
9112 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9113 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9114 procede = false;
9115 }
9116
9117 if (procede) {
9118 int dlg_return;
9119 m_FinishRouteOnKillFocus = false;
9120 if (m_routeState == 1) { // first point in new route, preceeding
9121 // route to be added? touch case
9122
9123 wxString dmsg =
9124 _("Insert first part of this route in the new route?");
9125 if (tail->GetIndexOf(pMousePoint) ==
9126 tail->GetnPoints()) // Starting on last point of another
9127 // route?
9128 dmsg = _("Insert this route in the new route?");
9129
9130 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9131 dlg_return =
9132 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9133 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9134 m_FinishRouteOnKillFocus = true;
9135
9136 if (dlg_return == wxID_YES) {
9137 inserting = true; // part of the other route will be
9138 // preceeding the new route
9139 }
9140 }
9141 } else {
9142 wxString dmsg =
9143 _("Append last part of this route to the new route?");
9144 if (tail->GetIndexOf(pMousePoint) == 1)
9145 dmsg = _(
9146 "Append this route to the new route?"); // Picking the
9147 // first point of
9148 // another route?
9149
9150 if (tail->GetLastPoint() != pMousePoint) { // 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 appending = true; // part of the other route will be
9158 // appended to the new route
9159 }
9160 }
9161 }
9162 }
9163
9164 // check all other routes to see if this point appears in any other
9165 // route If it appears in NO other route, then it should e
9166 // considered an isolated mark
9167 if (!FindRouteContainingWaypoint(pMousePoint))
9168 pMousePoint->SetShared(true);
9169 }
9170 }
9171
9172 if (NULL == pMousePoint) { // need a new point
9173 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9174 "", wxEmptyString);
9175 pMousePoint->SetNameShown(false);
9176
9177 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9178
9179 if (m_routeState > 1)
9180 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9181 Undo_IsOrphanded, NULL);
9182 }
9183
9184 if (m_routeState == 1) {
9185 // First point in the route.
9186 m_pMouseRoute->AddPoint(pMousePoint);
9187 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9188
9189 } else {
9190 if (m_pMouseRoute->m_NextLegGreatCircle) {
9191 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9192 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9193 &rhumbBearing, &rhumbDist);
9194 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9195 &gcDist, &gcBearing, NULL);
9196 double gcDistNM = gcDist / 1852.0;
9197
9198 // Empirically found expression to get reasonable route segments.
9199 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9200 pow(rhumbDist - gcDistNM - 1, 0.5);
9201
9202 wxString msg;
9203 msg << _("For this leg the Great Circle route is ")
9204 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9205 << _(" shorter than rhumbline.\n\n")
9206 << _("Would you like include the Great Circle routing points "
9207 "for this leg?");
9208
9209#ifndef __WXOSX__
9210 m_FinishRouteOnKillFocus = false;
9211 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9212 wxYES_NO | wxNO_DEFAULT);
9213 m_FinishRouteOnKillFocus = true;
9214#else
9215 int answer = wxID_NO;
9216#endif
9217
9218 if (answer == wxID_YES) {
9219 RoutePoint *gcPoint;
9220 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9221 wxRealPoint gcCoord;
9222
9223 for (int i = 1; i <= segmentCount; i++) {
9224 double fraction = (double)i * (1.0 / (double)segmentCount);
9225 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9226 gcDist * fraction, gcBearing,
9227 &gcCoord.x, &gcCoord.y, NULL);
9228
9229 if (i < segmentCount) {
9230 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9231 wxEmptyString);
9232 gcPoint->SetNameShown(false);
9233 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9234 gcPoint);
9235 } else {
9236 gcPoint = pMousePoint; // Last point, previously exsisting!
9237 }
9238
9239 m_pMouseRoute->AddPoint(gcPoint);
9240 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9241
9242 pSelect->AddSelectableRouteSegment(
9243 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9244 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9245 prevGcPoint = gcPoint;
9246 }
9247
9248 undo->CancelUndoableAction(true);
9249
9250 } else {
9251 m_pMouseRoute->AddPoint(pMousePoint);
9252 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9253 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9254 rlon, m_prev_pMousePoint,
9255 pMousePoint, m_pMouseRoute);
9256 undo->AfterUndoableAction(m_pMouseRoute);
9257 }
9258 } else {
9259 // Ordinary rhumblinesegment.
9260 m_pMouseRoute->AddPoint(pMousePoint);
9261 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9262
9263 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9264 rlon, m_prev_pMousePoint,
9265 pMousePoint, m_pMouseRoute);
9266 undo->AfterUndoableAction(m_pMouseRoute);
9267 }
9268 }
9269
9270 m_prev_rlat = rlat;
9271 m_prev_rlon = rlon;
9272 m_prev_pMousePoint = pMousePoint;
9273 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9274
9275 m_routeState++;
9276
9277 if (appending ||
9278 inserting) { // Appending a route or making a new route
9279 int connect = tail->GetIndexOf(pMousePoint);
9280 if (connect == 1) {
9281 inserting = false; // there is nothing to insert
9282 appending = true; // so append
9283 }
9284 int length = tail->GetnPoints();
9285
9286 int i;
9287 int start, stop;
9288 if (appending) {
9289 start = connect + 1;
9290 stop = length;
9291 } else { // inserting
9292 start = 1;
9293 stop = connect;
9294 m_pMouseRoute->RemovePoint(
9295 m_pMouseRoute
9296 ->GetLastPoint()); // Remove the first and only point
9297 }
9298 for (i = start; i <= stop; i++) {
9299 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9300 if (m_pMouseRoute)
9301 m_pMouseRoute->m_lastMousePointIndex =
9302 m_pMouseRoute->GetnPoints();
9303 m_routeState++;
9304 gFrame->RefreshAllCanvas();
9305 ret = true;
9306 }
9307 m_prev_rlat =
9308 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9309 m_prev_rlon =
9310 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9311 m_pMouseRoute->FinalizeForRendering();
9312 }
9313
9314 Refresh(true);
9315 ret = true;
9316 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9317 {
9318 if (m_bedge_pan) {
9319 m_bedge_pan = false;
9320 return false;
9321 }
9322
9323 if (m_nMeasureState == 1) {
9324 m_pMeasureRoute = new Route();
9325 pRouteList->push_back(m_pMeasureRoute);
9326 r_rband.x = x;
9327 r_rband.y = y;
9328 }
9329
9330 if (m_pMeasureRoute) {
9331 RoutePoint *pMousePoint =
9332 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9333 wxEmptyString, wxEmptyString);
9334 pMousePoint->m_bShowName = false;
9335
9336 m_pMeasureRoute->AddPoint(pMousePoint);
9337
9338 m_prev_rlat = m_cursor_lat;
9339 m_prev_rlon = m_cursor_lon;
9340 m_prev_pMousePoint = pMousePoint;
9341 m_pMeasureRoute->m_lastMousePointIndex =
9342 m_pMeasureRoute->GetnPoints();
9343
9344 m_nMeasureState++;
9345 } else {
9346 CancelMeasureRoute();
9347 }
9348
9349 Refresh(true);
9350 ret = true;
9351 } else {
9352 bool bSelectAllowed = true;
9353 if (NULL == g_pMarkInfoDialog) {
9354 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9355 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9356 bSelectAllowed = false;
9357
9358 // Avoid accidental selection of routepoint if last touchdown started
9359 // a significant chart drag operation
9360 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9361 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9362 significant_drag) ||
9363 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9364 significant_drag)) {
9365 bSelectAllowed = false;
9366 }
9367
9368 /*if this left up happens at the end of a route point dragging and if
9369 the cursor/thumb is on the draghandle icon, not on the point iself a new
9370 selection will select nothing and the drag will never be ended, so the
9371 legs around this point never selectable. At this step we don't need a
9372 new selection, just keep the previoulsly selected and dragged point */
9373 if (m_bRoutePoinDragging) bSelectAllowed = false;
9374
9375 if (bSelectAllowed) {
9376 bool b_was_editing_mark = m_bMarkEditing;
9377 bool b_was_editing_route = m_bRouteEditing;
9378 FindRoutePointsAtCursor(SelectRadius,
9379 true); // Possibly selecting a point in a
9380 // route for later dragging
9381
9382 /*route and a mark points in layer can't be dragged so should't be
9383 * selected and no draghandle icon*/
9384 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9385 m_pRoutePointEditTarget = NULL;
9386
9387 if (!b_was_editing_route) {
9388 if (m_pEditRouteArray) {
9389 b_startedit_route = true;
9390
9391 // Hide the track and route rollover during route point edit, not
9392 // needed, and may be confusing
9393 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9394 m_pTrackRolloverWin->IsActive(false);
9395 }
9396 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9397 m_pRouteRolloverWin->IsActive(false);
9398 }
9399
9400 wxRect pre_rect;
9401 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9402 ir++) {
9403 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9404 // Need to validate route pointer
9405 // Route may be gone due to drgging close to ownship with
9406 // "Delete On Arrival" state set, as in the case of
9407 // navigating to an isolated waypoint on a temporary route
9408 if (g_pRouteMan->IsRouteValid(pr)) {
9409 // pr->SetHiLite(50);
9410 wxRect route_rect;
9411 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9412 pre_rect.Union(route_rect);
9413 }
9414 }
9415 RefreshRect(pre_rect, true);
9416 }
9417 } else {
9418 b_startedit_route = false;
9419 }
9420
9421 // Mark editing in touch mode, left-up event.
9422 if (m_pRoutePointEditTarget) {
9423 if (b_was_editing_mark ||
9424 b_was_editing_route) { // kill previous hilight
9425 if (m_lastRoutePointEditTarget) {
9426 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9427 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9428 RoutePointGui(*m_lastRoutePointEditTarget)
9429 .EnableDragHandle(false);
9430 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9431 SELTYPE_DRAGHANDLE);
9432 }
9433 }
9434
9435 if (m_pRoutePointEditTarget) {
9436 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9437 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9438 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9439 wxPoint2DDouble dragHandlePoint =
9440 RoutePointGui(*m_pRoutePointEditTarget)
9441 .GetDragHandlePoint(this);
9442 pSelect->AddSelectablePoint(
9443 dragHandlePoint.m_y, dragHandlePoint.m_x,
9444 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9445 }
9446 } else { // Deselect everything
9447 if (m_lastRoutePointEditTarget) {
9448 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9449 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9450 RoutePointGui(*m_lastRoutePointEditTarget)
9451 .EnableDragHandle(false);
9452 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9453 SELTYPE_DRAGHANDLE);
9454
9455 // Clear any routes being edited, probably orphans
9456 wxArrayPtrVoid *lastEditRouteArray =
9458 m_lastRoutePointEditTarget);
9459 if (lastEditRouteArray) {
9460 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9461 ir++) {
9462 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9463 if (g_pRouteMan->IsRouteValid(pr)) {
9464 pr->m_bIsBeingEdited = false;
9465 }
9466 }
9467 delete lastEditRouteArray;
9468 }
9469 }
9470 }
9471
9472 // Do the refresh
9473
9474 if (g_bopengl) {
9475 InvalidateGL();
9476 Refresh(false);
9477 } else {
9478 if (m_lastRoutePointEditTarget) {
9479 wxRect wp_rect;
9480 RoutePointGui(*m_lastRoutePointEditTarget)
9481 .CalculateDCRect(m_dc_route, this, &wp_rect);
9482 RefreshRect(wp_rect, true);
9483 }
9484
9485 if (m_pRoutePointEditTarget) {
9486 wxRect wp_rect;
9487 RoutePointGui(*m_pRoutePointEditTarget)
9488 .CalculateDCRect(m_dc_route, this, &wp_rect);
9489 RefreshRect(wp_rect, true);
9490 }
9491 }
9492 }
9493 } // bSelectAllowed
9494
9495 // Check to see if there is a route or AIS target under the cursor
9496 // If so, start the rollover timer which creates the popup
9497 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9498 bool b_start_rollover = false;
9499 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9500 SelectItem *pFind = pSelectAIS->FindSelection(
9501 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9502 if (pFind) b_start_rollover = true;
9503 }
9504
9505 if (!b_start_rollover && !b_startedit_route) {
9506 SelectableItemList SelList = pSelect->FindSelectionList(
9507 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9508 for (SelectItem *pFindSel : SelList) {
9509 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9510 if (pr && pr->IsVisible()) {
9511 b_start_rollover = true;
9512 break;
9513 }
9514 } // while
9515 }
9516
9517 if (!b_start_rollover && !b_startedit_route) {
9518 SelectableItemList SelList = pSelect->FindSelectionList(
9519 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9520 for (SelectItem *pFindSel : SelList) {
9521 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9522 if (tr && tr->IsVisible()) {
9523 b_start_rollover = true;
9524 break;
9525 }
9526 } // while
9527 }
9528
9529 if (b_start_rollover)
9530 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9531 wxTIMER_ONE_SHOT);
9532 Route *tail = 0;
9533 Route *current = 0;
9534 bool appending = false;
9535 bool inserting = false;
9536 int connect = 0;
9537 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9538 // drag
9539 if (m_pRoutePointEditTarget) {
9540 // Check to see if there is a nearby point which may replace the
9541 // dragged one
9542 RoutePoint *pMousePoint = NULL;
9543
9544 int index_last;
9545 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9546 double nearby_radius_meters =
9547 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9548 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9549 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9550 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9551 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9552 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9553 bool duplicate =
9554 false; // ensure we won't create duplicate point in routes
9555 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9556 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9557 ir++) {
9558 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9559 if (pr && pr->pRoutePointList) {
9560 auto *list = pr->pRoutePointList;
9561 auto pos =
9562 std::find(list->begin(), list->end(), pNearbyPoint);
9563 if (pos != list->end()) {
9564 duplicate = true;
9565 break;
9566 }
9567 }
9568 }
9569 }
9570
9571 // Special case:
9572 // Allow "re-use" of a route's waypoints iff it is a simple
9573 // isolated route. This allows, for instance, creation of a closed
9574 // polygon route
9575 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9576
9577 if (!duplicate) {
9578 int dlg_return;
9579 dlg_return =
9580 OCPNMessageBox(this,
9581 _("Replace this RoutePoint by the nearby "
9582 "Waypoint?"),
9583 _("OpenCPN RoutePoint change"),
9584 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9585 if (dlg_return == wxID_YES) {
9586 /*double confirmation if the dragged point has been manually
9587 * created which can be important and could be deleted
9588 * unintentionally*/
9589
9590 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9591 pNearbyPoint);
9592 current =
9593 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9594
9595 if (tail && current && (tail != current)) {
9596 int dlg_return1;
9597 connect = tail->GetIndexOf(pNearbyPoint);
9598 int index_current_route =
9599 current->GetIndexOf(m_pRoutePointEditTarget);
9600 index_last = current->GetIndexOf(current->GetLastPoint());
9601 dlg_return1 = wxID_NO;
9602 if (index_last ==
9603 index_current_route) { // we are dragging the last
9604 // point of the route
9605 if (connect != tail->GetnPoints()) { // anything to do?
9606
9607 wxString dmsg(
9608 _("Last part of route to be appended to dragged "
9609 "route?"));
9610 if (connect == 1)
9611 dmsg =
9612 _("Full route to be appended to dragged route?");
9613
9614 dlg_return1 = OCPNMessageBox(
9615 this, dmsg, _("OpenCPN Route Create"),
9616 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9617 if (dlg_return1 == wxID_YES) {
9618 appending = true;
9619 }
9620 }
9621 } else if (index_current_route ==
9622 1) { // dragging the first point of the route
9623 if (connect != 1) { // anything to do?
9624
9625 wxString dmsg(
9626 _("First part of route to be inserted into dragged "
9627 "route?"));
9628 if (connect == tail->GetnPoints())
9629 dmsg = _(
9630 "Full route to be inserted into dragged route?");
9631
9632 dlg_return1 = OCPNMessageBox(
9633 this, dmsg, _("OpenCPN Route Create"),
9634 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9635 if (dlg_return1 == wxID_YES) {
9636 inserting = true;
9637 }
9638 }
9639 }
9640 }
9641
9642 if (m_pRoutePointEditTarget->IsShared()) {
9643 // dlg_return = wxID_NO;
9644 dlg_return = OCPNMessageBox(
9645 this,
9646 _("Do you really want to delete and replace this "
9647 "WayPoint") +
9648 "\n" + _("which has been created manually?"),
9649 ("OpenCPN RoutePoint warning"),
9650 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9651 }
9652 }
9653 if (dlg_return == wxID_YES) {
9654 pMousePoint = pNearbyPoint;
9655 if (pMousePoint->m_bIsolatedMark) {
9656 pMousePoint->SetShared(true);
9657 }
9658 pMousePoint->m_bIsolatedMark =
9659 false; // definitely no longer isolated
9660 pMousePoint->m_bIsInRoute = true;
9661 }
9662 }
9663 }
9664 }
9665 if (!pMousePoint)
9666 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9667
9668 if (m_pEditRouteArray) {
9669 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9670 ir++) {
9671 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9672 if (g_pRouteMan->IsRouteValid(pr)) {
9673 if (pMousePoint) { // remove the dragged point and insert the
9674 // nearby
9675 auto *list = pr->pRoutePointList;
9676 auto pos = std::find(list->begin(), list->end(),
9677 m_pRoutePointEditTarget);
9678
9679 pSelect->DeleteAllSelectableRoutePoints(pr);
9680 pSelect->DeleteAllSelectableRouteSegments(pr);
9681
9682 pr->pRoutePointList->insert(pos, pMousePoint);
9683 pos = std::find(list->begin(), list->end(),
9684 m_pRoutePointEditTarget);
9685 pr->pRoutePointList->erase(pos);
9686
9687 pSelect->AddAllSelectableRouteSegments(pr);
9688 pSelect->AddAllSelectableRoutePoints(pr);
9689 }
9690 pr->FinalizeForRendering();
9691 pr->UpdateSegmentDistances();
9692 if (m_bRoutePoinDragging) {
9693 // pConfig->UpdateRoute(pr);
9694 NavObj_dB::GetInstance().UpdateRoute(pr);
9695 }
9696 }
9697 }
9698 }
9699
9700 // Update the RouteProperties Dialog, if currently shown
9701 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9702 if (m_pEditRouteArray) {
9703 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9704 ir++) {
9705 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9706 if (g_pRouteMan->IsRouteValid(pr)) {
9707 if (pRoutePropDialog->GetRoute() == pr) {
9708 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9709 }
9710 /* cannot edit track points anyway
9711 else if ( ( NULL !=
9712 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9713 pTrackPropDialog->m_pTrack == pr ) {
9714 pTrackPropDialog->SetTrackAndUpdate(
9715 pr );
9716 }
9717 */
9718 }
9719 }
9720 }
9721 }
9722 if (pMousePoint) { // clear all about the dragged point
9723 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9724 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9725 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9726 // Hide mark properties dialog if open on the replaced point
9727 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9728 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9729 g_pMarkInfoDialog->Hide();
9730
9731 delete m_pRoutePointEditTarget;
9732 m_lastRoutePointEditTarget = NULL;
9733 m_pRoutePointEditTarget = NULL;
9734 undo->AfterUndoableAction(pMousePoint);
9735 undo->InvalidateUndo();
9736 }
9737 }
9738 }
9739
9740 else if (m_bMarkEditing) { // End of way point drag
9741 if (m_pRoutePointEditTarget)
9742 if (m_bRoutePoinDragging) {
9743 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9744 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9745 }
9746 }
9747
9748 if (m_pRoutePointEditTarget)
9749 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9750
9751 if (!m_pRoutePointEditTarget) {
9752 delete m_pEditRouteArray;
9753 m_pEditRouteArray = NULL;
9754 m_bRouteEditing = false;
9755 }
9756 m_bRoutePoinDragging = false;
9757
9758 if (appending) { // Appending to the route of which the last point is
9759 // dragged onto another route
9760
9761 // copy tail from connect until length to end of current after dragging
9762
9763 int length = tail->GetnPoints();
9764 for (int i = connect + 1; i <= length; i++) {
9765 current->AddPointAndSegment(tail->GetPoint(i), false);
9766 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9767 m_routeState++;
9768 gFrame->RefreshAllCanvas();
9769 ret = true;
9770 }
9771 current->FinalizeForRendering();
9772 current->m_bIsBeingEdited = false;
9773 FinishRoute();
9774 g_pRouteMan->DeleteRoute(tail);
9775 }
9776 if (inserting) {
9777 pSelect->DeleteAllSelectableRoutePoints(current);
9778 pSelect->DeleteAllSelectableRouteSegments(current);
9779 for (int i = 1; i < connect; i++) { // numbering in the tail route
9780 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9781 }
9782 pSelect->AddAllSelectableRouteSegments(current);
9783 pSelect->AddAllSelectableRoutePoints(current);
9784 current->FinalizeForRendering();
9785 current->m_bIsBeingEdited = false;
9786 g_pRouteMan->DeleteRoute(tail);
9787 }
9788
9789 // Update the RouteProperties Dialog, if currently shown
9790 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9791 if (m_pEditRouteArray) {
9792 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9793 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9794 if (g_pRouteMan->IsRouteValid(pr)) {
9795 if (pRoutePropDialog->GetRoute() == pr) {
9796 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9797 }
9798 }
9799 }
9800 }
9801 }
9802
9803 } // g_btouch
9804
9805 else { // !g_btouch
9806 if (m_bRouteEditing) { // End of RoutePoint drag
9807 Route *tail = 0;
9808 Route *current = 0;
9809 bool appending = false;
9810 bool inserting = false;
9811 int connect = 0;
9812 int index_last;
9813 if (m_pRoutePointEditTarget) {
9814 m_pRoutePointEditTarget->m_bBlink = false;
9815 // Check to see if there is a nearby point which may replace the
9816 // dragged one
9817 RoutePoint *pMousePoint = NULL;
9818 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9819 double nearby_radius_meters =
9820 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9821 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9822 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9823 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9824 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9825 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9826 bool duplicate = false; // don't create duplicate point in routes
9827 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9828 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9829 ir++) {
9830 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9831 if (pr && pr->pRoutePointList) {
9832 auto *list = pr->pRoutePointList;
9833 auto pos =
9834 std::find(list->begin(), list->end(), pNearbyPoint);
9835 if (pos != list->end()) {
9836 duplicate = true;
9837 break;
9838 }
9839 }
9840 }
9841 }
9842
9843 // Special case:
9844 // Allow "re-use" of a route's waypoints iff it is a simple
9845 // isolated route. This allows, for instance, creation of a closed
9846 // polygon route
9847 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9848
9849 if (!duplicate) {
9850 int dlg_return;
9851 dlg_return =
9852 OCPNMessageBox(this,
9853 _("Replace this RoutePoint by the nearby "
9854 "Waypoint?"),
9855 _("OpenCPN RoutePoint change"),
9856 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9857 if (dlg_return == wxID_YES) {
9858 /*double confirmation if the dragged point has been manually
9859 * created which can be important and could be deleted
9860 * unintentionally*/
9861 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9862 pNearbyPoint);
9863 current =
9864 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9865
9866 if (tail && current && (tail != current)) {
9867 int dlg_return1;
9868 connect = tail->GetIndexOf(pNearbyPoint);
9869 int index_current_route =
9870 current->GetIndexOf(m_pRoutePointEditTarget);
9871 index_last = current->GetIndexOf(current->GetLastPoint());
9872 dlg_return1 = wxID_NO;
9873 if (index_last ==
9874 index_current_route) { // we are dragging the last
9875 // point of the route
9876 if (connect != tail->GetnPoints()) { // anything to do?
9877
9878 wxString dmsg(
9879 _("Last part of route to be appended to dragged "
9880 "route?"));
9881 if (connect == 1)
9882 dmsg =
9883 _("Full route to be appended to dragged route?");
9884
9885 dlg_return1 = OCPNMessageBox(
9886 this, dmsg, _("OpenCPN Route Create"),
9887 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9888 if (dlg_return1 == wxID_YES) {
9889 appending = true;
9890 }
9891 }
9892 } else if (index_current_route ==
9893 1) { // dragging the first point of the route
9894 if (connect != 1) { // anything to do?
9895
9896 wxString dmsg(
9897 _("First part of route to be inserted into dragged "
9898 "route?"));
9899 if (connect == tail->GetnPoints())
9900 dmsg = _(
9901 "Full route to be inserted into dragged route?");
9902
9903 dlg_return1 = OCPNMessageBox(
9904 this, dmsg, _("OpenCPN Route Create"),
9905 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9906 if (dlg_return1 == wxID_YES) {
9907 inserting = true;
9908 }
9909 }
9910 }
9911 }
9912
9913 if (m_pRoutePointEditTarget->IsShared()) {
9914 dlg_return = wxID_NO;
9915 dlg_return = OCPNMessageBox(
9916 this,
9917 _("Do you really want to delete and replace this "
9918 "WayPoint") +
9919 "\n" + _("which has been created manually?"),
9920 ("OpenCPN RoutePoint warning"),
9921 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9922 }
9923 }
9924 if (dlg_return == wxID_YES) {
9925 pMousePoint = pNearbyPoint;
9926 if (pMousePoint->m_bIsolatedMark) {
9927 pMousePoint->SetShared(true);
9928 }
9929 pMousePoint->m_bIsolatedMark =
9930 false; // definitely no longer isolated
9931 pMousePoint->m_bIsInRoute = true;
9932 }
9933 }
9934 }
9935 }
9936 if (!pMousePoint)
9937 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9938
9939 if (m_pEditRouteArray) {
9940 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9941 ir++) {
9942 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9943 if (g_pRouteMan->IsRouteValid(pr)) {
9944 if (pMousePoint) { // replace dragged point by nearby one
9945 auto *list = pr->pRoutePointList;
9946 auto pos = std::find(list->begin(), list->end(),
9947 m_pRoutePointEditTarget);
9948
9949 pSelect->DeleteAllSelectableRoutePoints(pr);
9950 pSelect->DeleteAllSelectableRouteSegments(pr);
9951
9952 pr->pRoutePointList->insert(pos, pMousePoint);
9953 pos = std::find(list->begin(), list->end(),
9954 m_pRoutePointEditTarget);
9955 if (pos != list->end()) list->erase(pos);
9956 // pr->pRoutePointList->erase(pos + 1);
9957
9958 pSelect->AddAllSelectableRouteSegments(pr);
9959 pSelect->AddAllSelectableRoutePoints(pr);
9960 }
9961 pr->FinalizeForRendering();
9962 pr->UpdateSegmentDistances();
9963 pr->m_bIsBeingEdited = false;
9964
9965 if (m_bRoutePoinDragging) {
9966 // Special case optimization.
9967 // Dragging a single point of a route
9968 // without any point additions or re-ordering
9969 if (!pMousePoint)
9970 NavObj_dB::GetInstance().UpdateRoutePoint(
9971 m_pRoutePointEditTarget);
9972 else
9973 NavObj_dB::GetInstance().UpdateRoute(pr);
9974 }
9975 pr->SetHiLite(0);
9976 }
9977 }
9978 Refresh(false);
9979 }
9980
9981 if (appending) {
9982 // copy tail from connect until length to end of current after
9983 // dragging
9984
9985 int length = tail->GetnPoints();
9986 for (int i = connect + 1; i <= length; i++) {
9987 current->AddPointAndSegment(tail->GetPoint(i), false);
9988 if (current)
9989 current->m_lastMousePointIndex = current->GetnPoints();
9990 m_routeState++;
9991 gFrame->RefreshAllCanvas();
9992 ret = true;
9993 }
9994 current->FinalizeForRendering();
9995 current->m_bIsBeingEdited = false;
9996 FinishRoute();
9997 g_pRouteMan->DeleteRoute(tail);
9998 }
9999 if (inserting) {
10000 pSelect->DeleteAllSelectableRoutePoints(current);
10001 pSelect->DeleteAllSelectableRouteSegments(current);
10002 for (int i = 1; i < connect; i++) { // numbering in the tail route
10003 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10004 }
10005 pSelect->AddAllSelectableRouteSegments(current);
10006 pSelect->AddAllSelectableRoutePoints(current);
10007 current->FinalizeForRendering();
10008 current->m_bIsBeingEdited = false;
10009 g_pRouteMan->DeleteRoute(tail);
10010 }
10011
10012 // Update the RouteProperties Dialog, if currently shown
10013 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10014 if (m_pEditRouteArray) {
10015 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10016 ir++) {
10017 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10018 if (g_pRouteMan->IsRouteValid(pr)) {
10019 if (pRoutePropDialog->GetRoute() == pr) {
10020 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10021 }
10022 }
10023 }
10024 }
10025 }
10026
10027 if (pMousePoint) {
10028 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10029 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10030 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10031 // Hide mark properties dialog if open on the replaced point
10032 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10033 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10034 g_pMarkInfoDialog->Hide();
10035
10036 delete m_pRoutePointEditTarget;
10037 m_lastRoutePointEditTarget = NULL;
10038 undo->AfterUndoableAction(pMousePoint);
10039 undo->InvalidateUndo();
10040 } else {
10041 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10042 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10043
10044 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10045 }
10046
10047 delete m_pEditRouteArray;
10048 m_pEditRouteArray = NULL;
10049 }
10050
10051 InvalidateGL();
10052 m_bRouteEditing = false;
10053 m_pRoutePointEditTarget = NULL;
10054
10055 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10056 ret = true;
10057 }
10058
10059 else if (m_bMarkEditing) { // end of Waypoint drag
10060 if (m_pRoutePointEditTarget) {
10061 if (m_bRoutePoinDragging) {
10062 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10063 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10064 }
10065 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10066 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10067 if (!g_bopengl) {
10068 wxRect wp_rect;
10069 RoutePointGui(*m_pRoutePointEditTarget)
10070 .CalculateDCRect(m_dc_route, this, &wp_rect);
10071 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10072 RefreshRect(wp_rect, true);
10073 }
10074 }
10075 m_pRoutePointEditTarget = NULL;
10076 m_bMarkEditing = false;
10077 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10078 ret = true;
10079 }
10080
10081 else if (leftIsDown) { // left click for chart center
10082 leftIsDown = false;
10083 ret = false;
10084
10085 if (!g_btouch) {
10086 if (!m_bChartDragging && !m_bMeasure_Active) {
10087 } else {
10088 m_bChartDragging = false;
10089 }
10090 }
10091 }
10092 m_bRoutePoinDragging = false;
10093 } // !btouch
10094
10095 if (ret) return true;
10096 } // left up
10097
10098 if (event.RightDown()) {
10099 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10100 last_drag.x = mx;
10101 last_drag.y = my;
10102
10103 if (g_btouch) {
10104 // if( m_pRoutePointEditTarget )
10105 // return false;
10106 }
10107
10108 ret = true;
10109 m_FinishRouteOnKillFocus = false;
10110 CallPopupMenu(mx, my);
10111 m_FinishRouteOnKillFocus = true;
10112 } // Right down
10113
10114 return ret;
10115}
10116
10117bool panleftIsDown;
10118bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10119 // Skip all mouse processing if shift is held.
10120 // This allows plugins to implement shift+drag behaviors.
10121 if (event.ShiftDown()) {
10122 return false;
10123 }
10124 int x, y;
10125 event.GetPosition(&x, &y);
10126
10127 x *= m_displayScale;
10128 y *= m_displayScale;
10129
10130 // Check for wheel rotation
10131 // ideally, should be just longer than the time between
10132 // processing accumulated mouse events from the event queue
10133 // as would happen during screen redraws.
10134 int wheel_dir = event.GetWheelRotation();
10135
10136 if (wheel_dir) {
10137 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10138 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10139
10140 double factor = g_mouse_zoom_sensitivity;
10141 if (wheel_dir < 0) factor = 1 / factor;
10142
10143 if (g_bsmoothpanzoom) {
10144 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10145 if (wheel_dir == m_last_wheel_dir) {
10146 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10147 // m_zoom_target /= factor;
10148 } else
10149 StopMovement();
10150 } else {
10151 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10152 m_wheelstopwatch.Start(0);
10153 // m_zoom_target = VPoint.chart_scale / factor;
10154 }
10155 }
10156
10157 m_last_wheel_dir = wheel_dir;
10158
10159 ZoomCanvas(factor, true, false);
10160 }
10161
10162 if (event.LeftDown()) {
10163 // Skip the first left click if it will cause a canvas focus shift
10164 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10165 return false;
10166 }
10167
10168 last_drag.x = x, last_drag.y = y;
10169 panleftIsDown = true;
10170 }
10171
10172 if (event.LeftUp()) {
10173 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10174 // seen here.
10175 panleftIsDown = false;
10176
10177 if (!g_btouch) {
10178 if (!m_bChartDragging && !m_bMeasure_Active) {
10179 switch (cursor_region) {
10180 case MID_RIGHT: {
10181 PanCanvas(100, 0);
10182 break;
10183 }
10184
10185 case MID_LEFT: {
10186 PanCanvas(-100, 0);
10187 break;
10188 }
10189
10190 case MID_TOP: {
10191 PanCanvas(0, 100);
10192 break;
10193 }
10194
10195 case MID_BOT: {
10196 PanCanvas(0, -100);
10197 break;
10198 }
10199
10200 case CENTER: {
10201 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10202 break;
10203 }
10204 }
10205 } else {
10206 m_bChartDragging = false;
10207 }
10208 }
10209 }
10210 }
10211
10212 if (event.Dragging() && event.LeftIsDown()) {
10213 /*
10214 * fixed dragging.
10215 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10216 * before the drag event. Hence, as there is no mouse down event, last_drag
10217 * is not reset before the drag. And that results in one single drag
10218 * session, meaning you cannot drag the map a few miles north, lift your
10219 * finger, and the go even further north. Instead, the map resets itself
10220 * always to the very first drag start (since there is not reset of
10221 * last_drag).
10222 *
10223 * Besides, should not left down and dragging be enough of a situation to
10224 * start a drag procedure?
10225 *
10226 * Anyways, guarded it to be active in touch situations only.
10227 */
10228 if (g_btouch && !m_inPinch) {
10229 struct timespec now;
10230 clock_gettime(CLOCK_MONOTONIC, &now);
10231 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10232
10233 bool trigger_hold = false;
10234 if (false == m_bChartDragging) {
10235 if (m_DragTrigger < 0) {
10236 // printf("\ntrigger1\n");
10237 m_DragTrigger = 0;
10238 m_DragTriggerStartTime = tnow;
10239 trigger_hold = true;
10240 } else {
10241 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10242 m_DragTrigger = -1; // Reset trigger
10243 // printf("trigger fired\n");
10244 }
10245 }
10246 }
10247 if (trigger_hold) return true;
10248
10249 if (false == m_bChartDragging) {
10250 // printf("starting drag\n");
10251 // Reset drag calculation members
10252 last_drag.x = x - 1, last_drag.y = y - 1;
10253 m_bChartDragging = true;
10254 m_chart_drag_total_time = 0;
10255 m_chart_drag_total_x = 0;
10256 m_chart_drag_total_y = 0;
10257 m_inertia_last_drag_x = x;
10258 m_inertia_last_drag_y = y;
10259 m_drag_vec_x.clear();
10260 m_drag_vec_y.clear();
10261 m_drag_vec_t.clear();
10262 m_last_drag_time = tnow;
10263 }
10264
10265 // Calculate and store drag dynamics.
10266 uint64_t delta_t = tnow - m_last_drag_time;
10267 double delta_tf = delta_t / 1e9;
10268
10269 m_chart_drag_total_time += delta_tf;
10270 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10271 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10272
10273 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10274 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10275 m_drag_vec_t.push_back(delta_tf);
10276
10277 m_inertia_last_drag_x = x;
10278 m_inertia_last_drag_y = y;
10279 m_last_drag_time = tnow;
10280
10281 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10282 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10283 // dragging on route create.
10284 // github #2994
10285 m_bChartDragging = true;
10286 // printf("STM %d %d\n", last_drag.x - x, last_drag.y - y );
10287 StartTimedMovement();
10288 m_pan_drag.x += last_drag.x - x;
10289 m_pan_drag.y += last_drag.y - y;
10290 last_drag.x = x, last_drag.y = y;
10291 }
10292 }
10293 } else if (!g_btouch) {
10294 if ((last_drag.x != x) || (last_drag.y != y)) {
10295 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10296 // dragging on route create.
10297 // github #2994
10298 m_bChartDragging = true;
10299 StartTimedMovement();
10300 m_pan_drag.x += last_drag.x - x;
10301 m_pan_drag.y += last_drag.y - y;
10302 last_drag.x = x, last_drag.y = y;
10303 }
10304 }
10305 }
10306
10307 // Handle some special cases
10308 if (g_btouch) {
10309 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10310 // deactivate next LeftUp to ovoid creating an unexpected point
10311 m_DoubleClickTimer->Start();
10312 singleClickEventIsValid = false;
10313 }
10314 }
10315 }
10316
10317 return true;
10318}
10319
10320void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10321 if (MouseEventOverlayWindows(event)) return;
10322
10323 if (MouseEventSetup(event)) return; // handled, no further action required
10324
10325 bool nm = MouseEventProcessObjects(event);
10326 if (!nm) MouseEventProcessCanvas(event);
10327}
10328
10329void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10330 // Switch to the appropriate cursor on mouse movement
10331
10332 wxCursor *ptarget_cursor = pCursorArrow;
10333 if (!pPlugIn_Cursor) {
10334 ptarget_cursor = pCursorArrow;
10335 if ((!m_routeState) &&
10336 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10337 if (cursor_region == MID_RIGHT) {
10338 ptarget_cursor = pCursorRight;
10339 } else if (cursor_region == MID_LEFT) {
10340 ptarget_cursor = pCursorLeft;
10341 } else if (cursor_region == MID_TOP) {
10342 ptarget_cursor = pCursorDown;
10343 } else if (cursor_region == MID_BOT) {
10344 ptarget_cursor = pCursorUp;
10345 } else {
10346 ptarget_cursor = pCursorArrow;
10347 }
10348 } else if (m_bMeasure_Active ||
10349 m_routeState) // If Measure tool use Pencil Cursor
10350 ptarget_cursor = pCursorPencil;
10351 } else {
10352 ptarget_cursor = pPlugIn_Cursor;
10353 }
10354
10355 SetCursor(*ptarget_cursor);
10356}
10357
10358void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10359 SetCursor(*pCursorArrow);
10360}
10361
10362void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10363 ChartPlugInWrapper *target_plugin_chart = NULL;
10364 s57chart *Chs57 = NULL;
10365 wxFileName file;
10366 wxArrayString files;
10367
10368 ChartBase *target_chart = GetChartAtCursor();
10369 if (target_chart) {
10370 file.Assign(target_chart->GetFullPath());
10371 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10372 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10373 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10374 else
10375 Chs57 = dynamic_cast<s57chart *>(target_chart);
10376 } else { // target_chart = null, might be mbtiles
10377 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10378 unsigned int im = stackIndexArray.size();
10379 int scale = 2147483647; // max 32b integer
10380 if (VPoint.b_quilt && im > 0) {
10381 for (unsigned int is = 0; is < im; is++) {
10382 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10383 CHART_TYPE_MBTILES) {
10384 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10385 double lat, lon;
10386 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10387 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10388 .GetBBox()
10389 .Contains(lat, lon)) {
10390 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10391 scale) {
10392 scale =
10393 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10394 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10395 }
10396 }
10397 }
10398 }
10399 }
10400 }
10401
10402 std::vector<Ais8_001_22 *> area_notices;
10403
10404 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10405 float vp_scale = GetVPScale();
10406
10407 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10408 auto target_data = target.second;
10409 if (!target_data->area_notices.empty()) {
10410 for (auto &ani : target_data->area_notices) {
10411 Ais8_001_22 &area_notice = ani.second;
10412
10413 BoundingBox bbox;
10414
10415 for (Ais8_001_22_SubAreaList::iterator sa =
10416 area_notice.sub_areas.begin();
10417 sa != area_notice.sub_areas.end(); ++sa) {
10418 switch (sa->shape) {
10419 case AIS8_001_22_SHAPE_CIRCLE: {
10420 wxPoint target_point;
10421 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10422 bbox.Expand(target_point);
10423 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10424 break;
10425 }
10426 case AIS8_001_22_SHAPE_RECT: {
10427 wxPoint target_point;
10428 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10429 bbox.Expand(target_point);
10430 if (sa->e_dim_m > sa->n_dim_m)
10431 bbox.EnLarge(sa->e_dim_m * vp_scale);
10432 else
10433 bbox.EnLarge(sa->n_dim_m * vp_scale);
10434 break;
10435 }
10436 case AIS8_001_22_SHAPE_POLYGON:
10437 case AIS8_001_22_SHAPE_POLYLINE: {
10438 for (int i = 0; i < 4; ++i) {
10439 double lat = sa->latitude;
10440 double lon = sa->longitude;
10441 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10442 &lat, &lon);
10443 wxPoint target_point;
10444 GetCanvasPointPix(lat, lon, &target_point);
10445 bbox.Expand(target_point);
10446 }
10447 break;
10448 }
10449 case AIS8_001_22_SHAPE_SECTOR: {
10450 double lat1 = sa->latitude;
10451 double lon1 = sa->longitude;
10452 double lat, lon;
10453 wxPoint target_point;
10454 GetCanvasPointPix(lat1, lon1, &target_point);
10455 bbox.Expand(target_point);
10456 for (int i = 0; i < 18; ++i) {
10457 ll_gc_ll(
10458 lat1, lon1,
10459 sa->left_bound_deg +
10460 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10461 sa->radius_m / 1852.0, &lat, &lon);
10462 GetCanvasPointPix(lat, lon, &target_point);
10463 bbox.Expand(target_point);
10464 }
10465 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10466 &lat, &lon);
10467 GetCanvasPointPix(lat, lon, &target_point);
10468 bbox.Expand(target_point);
10469 break;
10470 }
10471 }
10472 }
10473
10474 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10475 area_notices.push_back(&area_notice);
10476 }
10477 }
10478 }
10479 }
10480 }
10481
10482 if (target_chart || !area_notices.empty() || file.HasName()) {
10483 // Go get the array of all objects at the cursor lat/lon
10484 int sel_rad_pix = 5;
10485 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10486
10487 // Make sure we always get the lights from an object, even if we are
10488 // currently not displaying lights on the chart.
10489
10490 SetCursor(wxCURSOR_WAIT);
10491 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10492 if (!lightsVis) SetShowENCLights(true);
10493 ;
10494
10495 ListOfObjRazRules *rule_list = NULL;
10496 ListOfPI_S57Obj *pi_rule_list = NULL;
10497 if (Chs57)
10498 rule_list =
10499 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10500 else if (target_plugin_chart)
10501 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10502 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10503
10504 ListOfObjRazRules *overlay_rule_list = NULL;
10505 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10506 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10507
10508 if (CHs57_Overlay) {
10509 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10510 zlat, zlon, SelectRadius, &GetVP());
10511 }
10512
10513 if (!lightsVis) SetShowENCLights(false);
10514
10515 wxString objText;
10516 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10517 wxString face = dFont->GetFaceName();
10518
10519 if (NULL == g_pObjectQueryDialog) {
10521 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10522 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10523 }
10524
10525 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10526 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10527
10528#ifdef __WXOSX__
10529 // Auto Adjustment for dark mode
10530 fg = g_pObjectQueryDialog->GetForegroundColour();
10531#endif
10532
10533 objText.Printf(
10534 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10535 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10536
10537#ifdef __WXOSX__
10538 int points = dFont->GetPointSize();
10539#else
10540 int points = dFont->GetPointSize() + 1;
10541#endif
10542
10543 int sizes[7];
10544 for (int i = -2; i < 5; i++) {
10545 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10546 }
10547 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10548
10549 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10550
10551 if (overlay_rule_list && CHs57_Overlay) {
10552 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10553 objText << "<hr noshade>";
10554 }
10555
10556 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10557 an != area_notices.end(); ++an) {
10558 objText << "<b>AIS Area Notice:</b> ";
10559 objText << ais8_001_22_notice_names[(*an)->notice_type];
10560 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10561 (*an)->sub_areas.begin();
10562 sa != (*an)->sub_areas.end(); ++sa)
10563 if (!sa->text.empty()) objText << sa->text;
10564 objText << "<br>expires: " << (*an)->expiry_time.Format();
10565 objText << "<hr noshade>";
10566 }
10567
10568 if (Chs57)
10569 objText << Chs57->CreateObjDescriptions(rule_list);
10570 else if (target_plugin_chart)
10571 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10572 pi_rule_list);
10573
10574 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10575
10576 // Add the additional info files
10577 wxString AddFiles, filenameOK;
10578 int filecount = 0;
10579 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10580 // plugin
10581
10582 AddFiles = wxString::Format(
10583 "<hr noshade><br><b>Additional info files attached to: </b> "
10584 "<font "
10585 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10586 "cellpadding=3>",
10587 file.GetFullName());
10588 file.Normalize();
10589 file.Assign(file.GetPath(), "");
10590 wxDir dir(file.GetFullPath());
10591 wxString filename;
10592 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10593 while (cont) {
10594 file.Assign(dir.GetNameWithSep().append(filename));
10595 wxString FormatString =
10596 "<td valign=top><font size=-2><a "
10597 "href=\"%s\">%s</a></font></td>";
10598 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10599 filenameOK = file.GetFullPath(); // remember last valid name
10600 // we are making a 3 columns table. New row only every third file
10601 if (3 * ((int)filecount / 3) == filecount)
10602 FormatString.Prepend("<tr>"); // new row
10603 else
10604 FormatString.Prepend(
10605 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10606 // spacer column
10607
10608 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10609 file.GetFullName());
10610 filecount++;
10611 }
10612 cont = dir.GetNext(&filename);
10613 }
10614 objText << AddFiles << "</table>";
10615 }
10616 objText << "</font>";
10617 objText << "</body></html>";
10618
10619 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10620 g_pObjectQueryDialog->SetHTMLPage(objText);
10621 g_pObjectQueryDialog->Show();
10622 }
10623 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10624 // generate an event to avoid double code
10625 wxHtmlLinkInfo hli(filenameOK);
10626 wxHtmlLinkEvent hle(1, hli);
10627 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10628 }
10629
10630 if (rule_list) rule_list->Clear();
10631 delete rule_list;
10632
10633 if (overlay_rule_list) overlay_rule_list->Clear();
10634 delete overlay_rule_list;
10635
10636 if (pi_rule_list) pi_rule_list->Clear();
10637 delete pi_rule_list;
10638
10639 SetCursor(wxCURSOR_ARROW);
10640 }
10641}
10642
10643void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10644 bool bNew = false;
10645 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10646 // Dialog
10647 g_pMarkInfoDialog = new MarkInfoDlg(this);
10648 bNew = true;
10649 }
10650
10651 if (1 /*g_bresponsive*/) {
10652 wxSize canvas_size = GetSize();
10653
10654 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10655 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10656
10657 g_pMarkInfoDialog->Layout();
10658
10659 wxPoint canvas_pos = GetPosition();
10660 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10661
10662 bool newFit = false;
10663 if (canvas_size.x < fitted_size.x) {
10664 fitted_size.x = canvas_size.x - 40;
10665 if (canvas_size.y < fitted_size.y)
10666 fitted_size.y -= 40; // scrollbar added
10667 }
10668 if (canvas_size.y < fitted_size.y) {
10669 fitted_size.y = canvas_size.y - 40;
10670 if (canvas_size.x < fitted_size.x)
10671 fitted_size.x -= 40; // scrollbar added
10672 }
10673
10674 if (newFit) {
10675 g_pMarkInfoDialog->SetSize(fitted_size);
10676 g_pMarkInfoDialog->Centre();
10677 }
10678 }
10679
10680 markPoint->m_bRPIsBeingEdited = false;
10681
10682 wxString title_base = _("Mark Properties");
10683 if (markPoint->m_bIsInRoute) {
10684 title_base = _("Waypoint Properties");
10685 }
10686 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10687 g_pMarkInfoDialog->UpdateProperties();
10688 if (markPoint->m_bIsInLayer) {
10689 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10690 GetLayerName(markPoint->m_LayerID)));
10691 g_pMarkInfoDialog->SetDialogTitle(caption);
10692 } else
10693 g_pMarkInfoDialog->SetDialogTitle(title_base);
10694
10695 g_pMarkInfoDialog->Show();
10696 g_pMarkInfoDialog->Raise();
10697 g_pMarkInfoDialog->InitialFocus();
10698 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10699}
10700
10701void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10702 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10703 pRoutePropDialog->SetRouteAndUpdate(selected);
10704 // pNew->UpdateProperties();
10705 pRoutePropDialog->Show();
10706 pRoutePropDialog->Raise();
10707 return;
10708 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10709 this); // There is one global instance of the RouteProp Dialog
10710
10711 if (g_bresponsive) {
10712 wxSize canvas_size = GetSize();
10713 wxPoint canvas_pos = GetPosition();
10714 wxSize fitted_size = pRoutePropDialog->GetSize();
10715 ;
10716
10717 if (canvas_size.x < fitted_size.x) {
10718 fitted_size.x = canvas_size.x;
10719 if (canvas_size.y < fitted_size.y)
10720 fitted_size.y -= 20; // scrollbar added
10721 }
10722 if (canvas_size.y < fitted_size.y) {
10723 fitted_size.y = canvas_size.y;
10724 if (canvas_size.x < fitted_size.x)
10725 fitted_size.x -= 20; // scrollbar added
10726 }
10727
10728 pRoutePropDialog->SetSize(fitted_size);
10729 pRoutePropDialog->Centre();
10730
10731 // int xp = (canvas_size.x - fitted_size.x)/2;
10732 // int yp = (canvas_size.y - fitted_size.y)/2;
10733
10734 wxPoint xxp = ClientToScreen(canvas_pos);
10735 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10736 }
10737
10738 pRoutePropDialog->SetRouteAndUpdate(selected);
10739
10740 pRoutePropDialog->Show();
10741
10742 Refresh(false);
10743}
10744
10745void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10746 pTrackPropDialog = TrackPropDlg::getInstance(
10747 this); // There is one global instance of the RouteProp Dialog
10748
10749 pTrackPropDialog->SetTrackAndUpdate(selected);
10751
10752 pTrackPropDialog->Show();
10753
10754 Refresh(false);
10755}
10756
10757void pupHandler_PasteWaypoint() {
10758 Kml kml;
10759
10760 int pasteBuffer = kml.ParsePasteBuffer();
10761 RoutePoint *pasted = kml.GetParsedRoutePoint();
10762 if (!pasted) return;
10763
10764 double nearby_radius_meters =
10765 g_Platform->GetSelectRadiusPix() /
10766 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10767
10768 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10769 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10770
10771 int answer = wxID_NO;
10772 if (nearPoint && !nearPoint->m_bIsInLayer) {
10773 wxString msg;
10774 msg << _(
10775 "There is an existing waypoint at the same location as the one you are "
10776 "pasting. Would you like to merge the pasted data with it?\n\n");
10777 msg << _("Answering 'No' will create a new waypoint at the same location.");
10778 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10779 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10780 }
10781
10782 if (answer == wxID_YES) {
10783 nearPoint->SetName(pasted->GetName());
10784 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10785 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10786 pRouteManagerDialog->UpdateWptListCtrl();
10787 }
10788
10789 if (answer == wxID_NO) {
10790 RoutePoint *newPoint = new RoutePoint(pasted);
10791 newPoint->m_bIsolatedMark = true;
10792 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10793 newPoint);
10794 // pConfig->AddNewWayPoint(newPoint, -1);
10795 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10796
10797 pWayPointMan->AddRoutePoint(newPoint);
10798 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10799 pRouteManagerDialog->UpdateWptListCtrl();
10800 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10801 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10802 }
10803
10804 gFrame->InvalidateAllGL();
10805 gFrame->RefreshAllCanvas(false);
10806}
10807
10808void pupHandler_PasteRoute() {
10809 Kml kml;
10810
10811 int pasteBuffer = kml.ParsePasteBuffer();
10812 Route *pasted = kml.GetParsedRoute();
10813 if (!pasted) return;
10814
10815 double nearby_radius_meters =
10816 g_Platform->GetSelectRadiusPix() /
10817 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10818
10819 RoutePoint *curPoint;
10820 RoutePoint *nearPoint;
10821 RoutePoint *prevPoint = NULL;
10822
10823 bool mergepoints = false;
10824 bool createNewRoute = true;
10825 int existingWaypointCounter = 0;
10826
10827 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10828 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10829 nearPoint = pWayPointMan->GetNearbyWaypoint(
10830 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10831 if (nearPoint) {
10832 mergepoints = true;
10833 existingWaypointCounter++;
10834 // Small hack here to avoid both extending RoutePoint and repeating all
10835 // the GetNearbyWaypoint calculations. Use existin data field in
10836 // RoutePoint as temporary storage.
10837 curPoint->m_bPtIsSelected = true;
10838 }
10839 }
10840
10841 int answer = wxID_NO;
10842 if (mergepoints) {
10843 wxString msg;
10844 msg << _(
10845 "There are existing waypoints at the same location as some of the ones "
10846 "you are pasting. Would you like to just merge the pasted data into "
10847 "them?\n\n");
10848 msg << _("Answering 'No' will create all new waypoints for this route.");
10849 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10850 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10851
10852 if (answer == wxID_CANCEL) {
10853 return;
10854 }
10855 }
10856
10857 // If all waypoints exist since before, and a route with the same name, we
10858 // don't create a new route.
10859 if (mergepoints && answer == wxID_YES &&
10860 existingWaypointCounter == pasted->GetnPoints()) {
10861 for (Route *proute : *pRouteList) {
10862 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10863 createNewRoute = false;
10864 break;
10865 }
10866 }
10867 }
10868
10869 Route *newRoute = 0;
10870 RoutePoint *newPoint = 0;
10871
10872 if (createNewRoute) {
10873 newRoute = new Route();
10874 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10875 }
10876
10877 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10878 curPoint = pasted->GetPoint(i);
10879 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10880 curPoint->m_bPtIsSelected = false;
10881 newPoint = pWayPointMan->GetNearbyWaypoint(
10882 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10883 newPoint->SetName(curPoint->GetName());
10884 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10885
10886 if (createNewRoute) newRoute->AddPoint(newPoint);
10887 } else {
10888 curPoint->m_bPtIsSelected = false;
10889
10890 newPoint = new RoutePoint(curPoint);
10891 newPoint->m_bIsolatedMark = false;
10892 newPoint->SetIconName("circle");
10893 newPoint->m_bIsVisible = true;
10894 newPoint->m_bShowName = false;
10895 newPoint->SetShared(false);
10896
10897 newRoute->AddPoint(newPoint);
10898 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10899 newPoint);
10900 // pConfig->AddNewWayPoint(newPoint, -1);
10901 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10902 pWayPointMan->AddRoutePoint(newPoint);
10903 }
10904 if (i > 1 && createNewRoute)
10905 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10906 curPoint->m_lat, curPoint->m_lon,
10907 prevPoint, newPoint, newRoute);
10908 prevPoint = newPoint;
10909 }
10910
10911 if (createNewRoute) {
10912 pRouteList->push_back(newRoute);
10913 // pConfig->AddNewRoute(newRoute); // use auto next num
10914 NavObj_dB::GetInstance().InsertRoute(newRoute);
10915
10916 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10917 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10918 }
10919
10920 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10921 pRouteManagerDialog->UpdateRouteListCtrl();
10922 pRouteManagerDialog->UpdateWptListCtrl();
10923 }
10924 gFrame->InvalidateAllGL();
10925 gFrame->RefreshAllCanvas(false);
10926 }
10927 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10928 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10929}
10930
10931void pupHandler_PasteTrack() {
10932 Kml kml;
10933
10934 int pasteBuffer = kml.ParsePasteBuffer();
10935 Track *pasted = kml.GetParsedTrack();
10936 if (!pasted) return;
10937
10938 TrackPoint *curPoint;
10939
10940 Track *newTrack = new Track();
10941 TrackPoint *newPoint;
10942 TrackPoint *prevPoint = NULL;
10943
10944 newTrack->SetName(pasted->GetName());
10945
10946 for (int i = 0; i < pasted->GetnPoints(); i++) {
10947 curPoint = pasted->GetPoint(i);
10948
10949 newPoint = new TrackPoint(curPoint);
10950
10951 wxDateTime now = wxDateTime::Now();
10952 newPoint->SetCreateTime(curPoint->GetCreateTime());
10953
10954 newTrack->AddPoint(newPoint);
10955
10956 if (prevPoint)
10957 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10958 newPoint->m_lat, newPoint->m_lon,
10959 prevPoint, newPoint, newTrack);
10960
10961 prevPoint = newPoint;
10962 }
10963
10964 g_TrackList.push_back(newTrack);
10965 // pConfig->AddNewTrack(newTrack);
10966 NavObj_dB::GetInstance().InsertTrack(newTrack);
10967
10968 gFrame->InvalidateAllGL();
10969 gFrame->RefreshAllCanvas(false);
10970}
10971
10972bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
10973 wxJSONValue v;
10974 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
10975 v["CursorPosition_x"] = x;
10976 v["CursorPosition_y"] = y;
10977 // Send a limited set of selection types depending on what is
10978 // found under the mouse point.
10979 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
10980 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
10981 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
10982
10983 wxJSONWriter w;
10984 wxString out;
10985 w.Write(v, out);
10986 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
10987
10988 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
10989
10990#if 0
10991#define SELTYPE_UNKNOWN 0x0001
10992#define SELTYPE_ROUTEPOINT 0x0002
10993#define SELTYPE_ROUTESEGMENT 0x0004
10994#define SELTYPE_TIDEPOINT 0x0008
10995#define SELTYPE_CURRENTPOINT 0x0010
10996#define SELTYPE_ROUTECREATE 0x0020
10997#define SELTYPE_AISTARGET 0x0040
10998#define SELTYPE_MARKPOINT 0x0080
10999#define SELTYPE_TRACKSEGMENT 0x0100
11000#define SELTYPE_DRAGHANDLE 0x0200
11001#endif
11002
11003 if (g_bhide_context_menus) return true;
11004 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11005 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11006 m_pIDXCandidate, m_nmea_log);
11007
11008 Connect(
11009 wxEVT_COMMAND_MENU_SELECTED,
11010 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11011
11012#ifdef __WXGTK__
11013 // Funny requirement here for gtk, to clear the menu trigger event
11014 // TODO
11015 // Causes a slight "flasH" of the menu,
11016 if (m_inLongPress) {
11017 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11018 m_inLongPress = false;
11019 }
11020#endif
11021
11022 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11023
11024 Disconnect(
11025 wxEVT_COMMAND_MENU_SELECTED,
11026 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11027
11028 delete m_canvasMenu;
11029 m_canvasMenu = NULL;
11030
11031#ifdef __WXQT__
11032 // gFrame->SurfaceToolbar();
11033 // g_MainToolbar->Raise();
11034#endif
11035
11036 return true;
11037}
11038
11039void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11040 // Pass menu events from the canvas to the menu handler
11041 // This is necessarily in ChartCanvas since that is the menu's parent.
11042 if (m_canvasMenu) {
11043 m_canvasMenu->PopupMenuHandler(event);
11044 }
11045 return;
11046}
11047
11048void ChartCanvas::StartRoute() {
11049 // Do not allow more than one canvas to create a route at one time.
11050 if (g_brouteCreating) return;
11051
11052 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11053
11054 g_brouteCreating = true;
11055 m_routeState = 1;
11056 m_bDrawingRoute = false;
11057 SetCursor(*pCursorPencil);
11058 // SetCanvasToolbarItemState(ID_ROUTE, true);
11059 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11060
11061 HideGlobalToolbar();
11062
11063#ifdef __ANDROID__
11064 androidSetRouteAnnunciator(true);
11065#endif
11066}
11067
11068wxString ChartCanvas::FinishRoute() {
11069 m_routeState = 0;
11070 m_prev_pMousePoint = NULL;
11071 m_bDrawingRoute = false;
11072 wxString rv = "";
11073 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11074
11075 // SetCanvasToolbarItemState(ID_ROUTE, false);
11076 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11077#ifdef __ANDROID__
11078 androidSetRouteAnnunciator(false);
11079#endif
11080
11081 SetCursor(*pCursorArrow);
11082
11083 if (m_pMouseRoute) {
11084 if (m_bAppendingRoute) {
11085 // pConfig->UpdateRoute(m_pMouseRoute);
11086 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11087 } else {
11088 if (m_pMouseRoute->GetnPoints() > 1) {
11089 // pConfig->AddNewRoute(m_pMouseRoute);
11090 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11091 } else {
11092 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11093 m_pMouseRoute = NULL;
11094 }
11095 }
11096 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11097
11098 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11099 (pRoutePropDialog->IsShown())) {
11100 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11101 }
11102
11103 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11104 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11105 pRouteManagerDialog->UpdateRouteListCtrl();
11106 }
11107 }
11108 m_bAppendingRoute = false;
11109 m_pMouseRoute = NULL;
11110
11111 m_pSelectedRoute = NULL;
11112
11113 undo->InvalidateUndo();
11114 gFrame->RefreshAllCanvas(true);
11115
11116 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11117
11118 ShowGlobalToolbar();
11119
11120 g_brouteCreating = false;
11121
11122 return rv;
11123}
11124
11125void ChartCanvas::HideGlobalToolbar() {
11126 if (m_canvasIndex == 0) {
11127 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
11128 }
11129}
11130
11131void ChartCanvas::ShowGlobalToolbar() {
11132 if (m_canvasIndex == 0) {
11133 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
11134 }
11135}
11136
11137void ChartCanvas::ShowAISTargetList() {
11138 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11139 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11140 }
11141
11142 g_pAISTargetList->UpdateAISTargetList();
11143}
11144
11145void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11146 if (!m_bShowOutlines) return;
11147
11148 if (!ChartData) return;
11149
11150 int nEntry = ChartData->GetChartTableEntries();
11151
11152 for (int i = 0; i < nEntry; i++) {
11153 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11154
11155 // Check to see if the candidate chart is in the currently active group
11156 bool b_group_draw = false;
11157 if (m_groupIndex > 0) {
11158 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11159 int index = pt->GetGroupArray()[ig];
11160 if (m_groupIndex == index) {
11161 b_group_draw = true;
11162 break;
11163 }
11164 }
11165 } else
11166 b_group_draw = true;
11167
11168 if (b_group_draw) RenderChartOutline(dc, i, vp);
11169 }
11170
11171 // On CM93 Composite Charts, draw the outlines of the next smaller
11172 // scale cell
11173 cm93compchart *pcm93 = NULL;
11174 if (VPoint.b_quilt) {
11175 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11176 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11177 pcm93 = (cm93compchart *)pch;
11178 break;
11179 }
11180 } else if (m_singleChart &&
11181 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11182 pcm93 = (cm93compchart *)m_singleChart;
11183
11184 if (pcm93) {
11185 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11186 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11187
11188 if (zoom_factor > 8.0) {
11189 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11190 dc.SetPen(mPen);
11191 } else {
11192 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11193 dc.SetPen(mPen);
11194 }
11195
11196 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11197 }
11198}
11199
11200void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11201#ifdef ocpnUSE_GL
11202 if (g_bopengl && m_glcc) {
11203 /* opengl version specially optimized */
11204 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11205 return;
11206 }
11207#endif
11208
11209 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11210 if (!ChartData->IsChartAvailable(dbIndex)) return;
11211 }
11212
11213 float plylat, plylon;
11214 float plylat1, plylon1;
11215
11216 int pixx, pixy, pixx1, pixy1;
11217
11218 LLBBox box;
11219 ChartData->GetDBBoundingBox(dbIndex, box);
11220
11221 // Don't draw an outline in the case where the chart covers the entire world
11222 // */
11223 if (box.GetLonRange() == 360) return;
11224
11225 double lon_bias = 0;
11226 // chart is outside of viewport lat/lon bounding box
11227 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11228
11229 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11230
11231 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11232 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11233
11234 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11235 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11236
11237 else
11238 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11239
11240 // Are there any aux ply entries?
11241 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11242 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11243 {
11244 wxPoint r, r1;
11245
11246 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11247 plylon += lon_bias;
11248
11249 GetCanvasPointPix(plylat, plylon, &r);
11250 pixx = r.x;
11251 pixy = r.y;
11252
11253 for (int i = 0; i < nPly - 1; i++) {
11254 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11255 plylon1 += lon_bias;
11256
11257 GetCanvasPointPix(plylat1, plylon1, &r1);
11258 pixx1 = r1.x;
11259 pixy1 = r1.y;
11260
11261 int pixxs1 = pixx1;
11262 int pixys1 = pixy1;
11263
11264 bool b_skip = false;
11265
11266 if (vp.chart_scale > 5e7) {
11267 // calculate projected distance between these two points in meters
11268 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11269 pow((double)(pixy1 - pixy), 2)) /
11270 vp.view_scale_ppm;
11271
11272 if (dist > 0.0) {
11273 // calculate GC distance between these two points in meters
11274 double distgc =
11275 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11276
11277 // If the distances are nonsense, it means that the scale is very
11278 // small and the segment wrapped the world So skip it....
11279 // TODO improve this to draw two segments
11280 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11281 b_skip = true;
11282 } else
11283 b_skip = true;
11284 }
11285
11286 ClipResult res = cohen_sutherland_line_clip_i(
11287 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11288 if (res != Invisible && !b_skip)
11289 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11290
11291 plylat = plylat1;
11292 plylon = plylon1;
11293 pixx = pixxs1;
11294 pixy = pixys1;
11295 }
11296
11297 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11298 plylon1 += lon_bias;
11299
11300 GetCanvasPointPix(plylat1, plylon1, &r1);
11301 pixx1 = r1.x;
11302 pixy1 = r1.y;
11303
11304 ClipResult res = cohen_sutherland_line_clip_i(
11305 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11306 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11307 }
11308
11309 else // Use Aux PlyPoints
11310 {
11311 wxPoint r, r1;
11312
11313 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11314 for (int j = 0; j < nAuxPlyEntries; j++) {
11315 int nAuxPly =
11316 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11317 GetCanvasPointPix(plylat, plylon, &r);
11318 pixx = r.x;
11319 pixy = r.y;
11320
11321 for (int i = 0; i < nAuxPly - 1; i++) {
11322 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11323
11324 GetCanvasPointPix(plylat1, plylon1, &r1);
11325 pixx1 = r1.x;
11326 pixy1 = r1.y;
11327
11328 int pixxs1 = pixx1;
11329 int pixys1 = pixy1;
11330
11331 bool b_skip = false;
11332
11333 if (vp.chart_scale > 5e7) {
11334 // calculate projected distance between these two points in meters
11335 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11336 ((pixy1 - pixy) * (pixy1 - pixy))) /
11337 vp.view_scale_ppm;
11338 if (dist > 0.0) {
11339 // calculate GC distance between these two points in meters
11340 double distgc =
11341 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11342
11343 // If the distances are nonsense, it means that the scale is very
11344 // small and the segment wrapped the world So skip it....
11345 // TODO improve this to draw two segments
11346 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11347 b_skip = true;
11348 } else
11349 b_skip = true;
11350 }
11351
11352 ClipResult res = cohen_sutherland_line_clip_i(
11353 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11354 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11355
11356 plylat = plylat1;
11357 plylon = plylon1;
11358 pixx = pixxs1;
11359 pixy = pixys1;
11360 }
11361
11362 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11363 GetCanvasPointPix(plylat1, plylon1, &r1);
11364 pixx1 = r1.x;
11365 pixy1 = r1.y;
11366
11367 ClipResult res = cohen_sutherland_line_clip_i(
11368 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11369 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11370 }
11371 }
11372}
11373
11374static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
11375 const wxString &second) {
11376 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11377
11378 int pointsize = dFont->GetPointSize();
11379 pointsize /= OCPN_GetWinDIPScaleFactor();
11380
11381 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11382 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11383 false, dFont->GetFaceName());
11384
11385 dc.SetFont(*psRLI_font);
11386
11387 int w1, h1;
11388 int w2 = 0;
11389 int h2 = 0;
11390 int h, w;
11391
11392 int xp, yp;
11393 int hilite_offset = 3;
11394#ifdef __WXMAC__
11395 wxScreenDC sdc;
11396 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
11397 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
11398#else
11399 dc.GetTextExtent(first, &w1, &h1);
11400 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
11401#endif
11402
11403 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11404 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11405
11406 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
11407 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11408
11409 h = h1 + h2;
11410
11411 xp = ref_point.x - w;
11412 yp = ref_point.y;
11413 yp += hilite_offset;
11414
11415 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11416
11417 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11418 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11419
11420 dc.DrawText(first, xp, yp);
11421 if (second.Len()) dc.DrawText(second, xp, yp + h1);
11422}
11423
11424void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11425 if (!g_bAllowShipToActive) return;
11426
11427 Route *rt = g_pRouteMan->GetpActiveRoute();
11428 if (!rt) return;
11429
11430 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11431 wxPoint2DDouble pa, pb;
11433 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11434
11435 // set pen
11436 int width =
11437 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11438 if (rt->m_width != wxPENSTYLE_INVALID)
11439 width = rt->m_width; // set route pen style if any
11440 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11441 g_shipToActiveStyle, 5)]; // get setting pen style
11442 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11443 wxColour color =
11444 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11445 : // set setting route pen color
11446 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11447 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11448
11449 dc.SetPen(*mypen);
11450 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11451
11452 if (!Use_Opengl)
11453 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11454 (int)pb.m_y, GetVP(), true);
11455
11456#ifdef ocpnUSE_GL
11457 else {
11458#ifdef USE_ANDROID_GLES2
11459 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11460#else
11461 if (style != wxPENSTYLE_SOLID) {
11462 if (glChartCanvas::dash_map.find(style) !=
11463 glChartCanvas::dash_map.end()) {
11464 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11465 dc.SetPen(*mypen);
11466 }
11467 }
11468 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11469#endif
11470
11471 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11472 (int)pb.m_x, (int)pb.m_y, GetVP());
11473 }
11474#endif
11475 }
11476}
11477
11478void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11479 Route *route = 0;
11480 if (m_routeState >= 2) route = m_pMouseRoute;
11481 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11482 route = m_pMeasureRoute;
11483
11484 if (!route) return;
11485
11486 // Validate route pointer
11487 if (!g_pRouteMan->IsRouteValid(route)) return;
11488
11489 double render_lat = m_cursor_lat;
11490 double render_lon = m_cursor_lon;
11491
11492 int np = route->GetnPoints();
11493 if (np) {
11494 if (g_btouch && (np > 1)) np--;
11495 RoutePoint rp = route->GetPoint(np);
11496 render_lat = rp.m_lat;
11497 render_lon = rp.m_lon;
11498 }
11499
11500 double rhumbBearing, rhumbDist;
11501 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11502 &rhumbBearing, &rhumbDist);
11503 double brg = rhumbBearing;
11504 double dist = rhumbDist;
11505
11506 // Skip GreatCircle rubberbanding on touch devices.
11507 if (!g_btouch) {
11508 double gcBearing, gcBearing2, gcDist;
11509 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11510 m_cursor_lat, &gcDist, &gcBearing,
11511 &gcBearing2);
11512 double gcDistm = gcDist / 1852.0;
11513
11514 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11515 rhumbBearing = 90.;
11516
11517 wxPoint destPoint, lastPoint;
11518
11519 route->m_NextLegGreatCircle = false;
11520 int milesDiff = rhumbDist - gcDistm;
11521 if (milesDiff > 1) {
11522 brg = gcBearing;
11523 dist = gcDistm;
11524 route->m_NextLegGreatCircle = true;
11525 }
11526
11527 // FIXME (MacOS, the first segment is rendered wrong)
11528 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11529 &lastPoint);
11530
11531 if (route->m_NextLegGreatCircle) {
11532 for (int i = 1; i <= milesDiff; i++) {
11533 double p = (double)i * (1.0 / (double)milesDiff);
11534 double pLat, pLon;
11535 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11536 &pLon, &pLat, &gcBearing2);
11537 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11538 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11539 false);
11540 lastPoint = destPoint;
11541 }
11542 } else {
11543 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11544 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11545 false);
11546 if (m_bMeasure_DistCircle) {
11547 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11548 powf((float)(r_rband.y - lastPoint.y), 2));
11549
11550 dc.SetPen(*g_pRouteMan->GetRoutePen());
11551 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11552 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11553 }
11554 }
11555 }
11556 }
11557
11558 wxString routeInfo;
11559 double varBrg = 0;
11560 if (g_bShowTrue)
11561 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11562 0x00B0);
11563
11564 if (g_bShowMag) {
11565 double latAverage = (m_cursor_lat + render_lat) / 2;
11566 double lonAverage = (m_cursor_lon + render_lon) / 2;
11567 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11568
11569 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11570 (int)varBrg, 0x00B0);
11571 }
11572 routeInfo << " " << FormatDistanceAdaptive(dist);
11573
11574 // To make it easier to use a route as a bearing on a charted object add for
11575 // the first leg also the reverse bearing.
11576 if (np == 1) {
11577 routeInfo << "\nReverse: ";
11578 if (g_bShowTrue)
11579 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11580 (int)(brg + 180.) % 360, 0x00B0);
11581 if (g_bShowMag)
11582 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11583 (int)(varBrg + 180.) % 360, 0x00B0);
11584 }
11585
11586 wxString s0;
11587 if (!route->m_bIsInLayer)
11588 s0.Append(_("Route") + ": ");
11589 else
11590 s0.Append(_("Layer Route: "));
11591
11592 double disp_length = route->m_route_length;
11593 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11594 s0 += FormatDistanceAdaptive(disp_length);
11595
11596 RouteLegInfo(dc, r_rband, routeInfo, s0);
11597
11598 m_brepaint_piano = true;
11599}
11600
11601void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11602 if (!m_bShowVisibleSectors) return;
11603
11604 if (g_bDeferredInitDone) {
11605 // need to re-evaluate sectors?
11606 double rhumbBearing, rhumbDist;
11607 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11608 &rhumbBearing, &rhumbDist);
11609
11610 if (rhumbDist > 0.05) // miles
11611 {
11612 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11613 m_sectorlegsVisible);
11614 m_sector_glat = gLat;
11615 m_sector_glon = gLon;
11616 }
11617 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11618 }
11619}
11620
11621void ChartCanvas::WarpPointerDeferred(int x, int y) {
11622 warp_x = x;
11623 warp_y = y;
11624 warp_flag = true;
11625}
11626
11627int s_msg;
11628
11629void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11630 if (!ps52plib) return;
11631
11632 if (VPoint.b_quilt) { // quilted
11633 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11634
11635 if (m_pQuilt->IsQuiltVector()) {
11636 if (ps52plib->GetStateHash() != m_s52StateHash) {
11637 UpdateS52State();
11638 m_s52StateHash = ps52plib->GetStateHash();
11639 }
11640 }
11641 } else {
11642 if (ps52plib->GetStateHash() != m_s52StateHash) {
11643 UpdateS52State();
11644 m_s52StateHash = ps52plib->GetStateHash();
11645 }
11646 }
11647
11648 // Plugin charts
11649 bool bSendPlibState = true;
11650 if (VPoint.b_quilt) { // quilted
11651 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11652 }
11653
11654 if (bSendPlibState) {
11655 wxJSONValue v;
11656 v["OpenCPN Version Major"] = VERSION_MAJOR;
11657 v["OpenCPN Version Minor"] = VERSION_MINOR;
11658 v["OpenCPN Version Patch"] = VERSION_PATCH;
11659 v["OpenCPN Version Date"] = VERSION_DATE;
11660 v["OpenCPN Version Full"] = VERSION_FULL;
11661
11662 // S52PLIB state
11663 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11664 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11665 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11666 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11667 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11668 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11669 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11670
11671 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11672
11673 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11674 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11675
11676 // Global S52 options
11677
11678 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11679 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11680 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11681 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11682 ps52plib->m_bShowS57ImportantTextOnly;
11683 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11684 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11685 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11686 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11687 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11688
11689 // Some global GUI parameters, for completeness
11690 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11691 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11692 v["OpenCPN Scale Factor Exp"] =
11693 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11694 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11695
11696 wxJSONWriter w;
11697 wxString out;
11698 w.Write(v, out);
11699
11700 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11701 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11702 g_lastS52PLIBPluginMessage = out;
11703 }
11704 }
11705}
11706int spaint;
11707int s_in_update;
11708void ChartCanvas::OnPaint(wxPaintEvent &event) {
11709 wxPaintDC dc(this);
11710
11711 // GetToolbar()->Show( m_bToolbarEnable );
11712
11713 // Paint updates may have been externally disabled (temporarily, to avoid
11714 // Yield() recursion performance loss) It is important that the wxPaintDC is
11715 // built, even if we elect to not process this paint message. Otherwise, the
11716 // paint message may not be removed from the message queue, esp on Windows.
11717 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11718
11719 if (!m_b_paint_enable) {
11720 return;
11721 }
11722
11723 // If necessary, reconfigure the S52 PLIB
11725
11726#ifdef ocpnUSE_GL
11727 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11728
11729 if (m_glcc && g_bopengl) {
11730 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11731 s_in_update++;
11732 m_glcc->Update();
11733 s_in_update--;
11734 }
11735
11736 return;
11737 }
11738#endif
11739
11740 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11741
11742 wxRegion ru = GetUpdateRegion();
11743
11744 int rx, ry, rwidth, rheight;
11745 ru.GetBox(rx, ry, rwidth, rheight);
11746
11747#ifdef ocpnUSE_DIBSECTION
11748 ocpnMemDC temp_dc;
11749#else
11750 wxMemoryDC temp_dc;
11751#endif
11752
11753 long height = GetVP().pix_height;
11754
11755#ifdef __WXMAC__
11756 // On OS X we have to explicitly extend the region for the piano area
11757 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11758 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11759 height += m_Piano->GetHeight();
11760#endif // __WXMAC__
11761 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11762
11763 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11764 if (pthumbwin) {
11765 int thumbx, thumby, thumbsx, thumbsy;
11766 pthumbwin->GetPosition(&thumbx, &thumby);
11767 pthumbwin->GetSize(&thumbsx, &thumbsy);
11768 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11769
11770 if (pthumbwin->IsShown()) {
11771 rgn_chart.Subtract(rgn_thumbwin);
11772 ru.Subtract(rgn_thumbwin);
11773 }
11774 }
11775
11776 // subtract the chart bar if it isn't transparent, and determine if we need to
11777 // paint it
11778 wxRegion rgn_blit = ru;
11779 if (g_bShowChartBar) {
11780 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11781 GetClientSize().x, m_Piano->GetHeight());
11782
11783 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11784 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11785 if (style->chartStatusWindowTransparent)
11786 m_brepaint_piano = true;
11787 else
11788 ru.Subtract(chart_bar_rect);
11789 }
11790 }
11791
11792 if (m_Compass && m_Compass->IsShown()) {
11793 wxRect compassRect = m_Compass->GetRect();
11794 if (ru.Contains(compassRect) != wxOutRegion) {
11795 ru.Subtract(compassRect);
11796 }
11797 }
11798
11799 if (m_notification_button) {
11800 wxRect noteRect = m_notification_button->GetRect();
11801 if (ru.Contains(noteRect) != wxOutRegion) {
11802 ru.Subtract(noteRect);
11803 }
11804 }
11805
11806 // Is this viewpoint the same as the previously painted one?
11807 bool b_newview = true;
11808
11809 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11810 (m_cache_vp.rotation == VPoint.rotation) &&
11811 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11812 m_cache_vp.IsValid()) {
11813 b_newview = false;
11814 }
11815
11816 // If the ViewPort is skewed or rotated, we may be able to use the cached
11817 // rotated bitmap.
11818 bool b_rcache_ok = false;
11819 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11820 b_rcache_ok = !b_newview;
11821
11822 // Make a special VP
11823 if (VPoint.b_MercatorProjectionOverride)
11824 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11825 ViewPort svp = VPoint;
11826
11827 svp.pix_width = svp.rv_rect.width;
11828 svp.pix_height = svp.rv_rect.height;
11829
11830 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11831 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11832 // VPoint.rv_rect.height);
11833
11834 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11835
11836 // If we are going to use the cached rotated image, there is no need to fetch
11837 // any chart data and this will do it...
11838 if (b_rcache_ok) chart_get_region.Clear();
11839
11840 // Blit pan acceleration
11841 if (VPoint.b_quilt) // quilted
11842 {
11843 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11844
11845 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11846
11847 bool busy = false;
11848 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11849 m_cache_vp.rotation != VPoint.rotation)) {
11850 AbstractPlatform::ShowBusySpinner();
11851 busy = true;
11852 }
11853
11854 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11855 (m_working_bm.GetHeight() != svp.pix_height))
11856 m_working_bm.Create(svp.pix_width, svp.pix_height,
11857 -1); // make sure the target is big enoug
11858
11859 if (fabs(VPoint.rotation) < 0.01) {
11860 bool b_save = true;
11861
11862 if (g_SencThreadManager) {
11863 if (g_SencThreadManager->GetJobCount()) {
11864 b_save = false;
11865 m_cache_vp.Invalidate();
11866 }
11867 }
11868
11869 // If the saved wxBitmap from last OnPaint is useable
11870 // calculate the blit parameters
11871
11872 // We can only do screen blit painting if subsequent ViewPorts differ by
11873 // whole pixels So, in small scale bFollow mode, force the full screen
11874 // render. This seems a hack....There may be better logic here.....
11875
11876 // if(m_bFollow)
11877 // b_save = false;
11878
11879 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11880 if (b_newview) {
11881 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11882 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11883
11884 int dy = c_new.y - c_old.y;
11885 int dx = c_new.x - c_old.x;
11886
11887 // printf("In OnPaint Trying Blit dx: %d
11888 // dy:%d\n\n", dx, dy);
11889
11890 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11891 if (dx || dy) {
11892 // Blit the reuseable portion of the cached wxBitmap to a working
11893 // bitmap
11894 temp_dc.SelectObject(m_working_bm);
11895
11896 wxMemoryDC cache_dc;
11897 cache_dc.SelectObject(m_cached_chart_bm);
11898
11899 if (dy > 0) {
11900 if (dx > 0) {
11901 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11902 VPoint.pix_height - dy, &cache_dc, dx, dy);
11903 } else {
11904 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11905 VPoint.pix_height - dy, &cache_dc, 0, dy);
11906 }
11907
11908 } else {
11909 if (dx > 0) {
11910 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11911 VPoint.pix_height + dy, &cache_dc, dx, 0);
11912 } else {
11913 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11914 VPoint.pix_height + dy, &cache_dc, 0, 0);
11915 }
11916 }
11917
11918 OCPNRegion update_region;
11919 if (dy) {
11920 if (dy > 0)
11921 update_region.Union(
11922 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11923 else
11924 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11925 }
11926
11927 if (dx) {
11928 if (dx > 0)
11929 update_region.Union(
11930 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11931 else
11932 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11933 }
11934
11935 // Render the new region
11936 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11937 update_region);
11938 cache_dc.SelectObject(wxNullBitmap);
11939 } else {
11940 // No sensible (dx, dy) change in the view, so use the cached
11941 // member bitmap
11942 temp_dc.SelectObject(m_cached_chart_bm);
11943 b_save = false;
11944 }
11945 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11946
11947 } else // not blitable
11948 {
11949 temp_dc.SelectObject(m_working_bm);
11950 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11951 chart_get_region);
11952 }
11953 } else {
11954 // No change in the view, so use the cached member bitmap2
11955 temp_dc.SelectObject(m_cached_chart_bm);
11956 b_save = false;
11957 }
11958 } else // cached bitmap is not yet valid
11959 {
11960 temp_dc.SelectObject(m_working_bm);
11961 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11962 chart_get_region);
11963 }
11964
11965 // Save the fully rendered quilt image as a wxBitmap member of this class
11966 if (b_save) {
11967 // if((m_cached_chart_bm.GetWidth() !=
11968 // svp.pix_width) ||
11969 // (m_cached_chart_bm.GetHeight() !=
11970 // svp.pix_height))
11971 // m_cached_chart_bm.Create(svp.pix_width,
11972 // svp.pix_height, -1); // target wxBitmap
11973 // is big enough
11974 wxMemoryDC scratch_dc_0;
11975 scratch_dc_0.SelectObject(m_cached_chart_bm);
11976 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11977
11978 scratch_dc_0.SelectObject(wxNullBitmap);
11979
11980 m_bm_cache_vp =
11981 VPoint; // save the ViewPort associated with the cached wxBitmap
11982 }
11983 }
11984
11985 else // quilted, rotated
11986 {
11987 temp_dc.SelectObject(m_working_bm);
11988 OCPNRegion chart_get_all_region(
11989 wxRect(0, 0, svp.pix_width, svp.pix_height));
11990 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11991 chart_get_all_region);
11992 }
11993
11994 AbstractPlatform::HideBusySpinner();
11995
11996 }
11997
11998 else // not quilted
11999 {
12000 if (!m_singleChart) {
12001 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12002 dc.Clear();
12003 return;
12004 }
12005
12006 if (!chart_get_region.IsEmpty()) {
12007 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12008 }
12009 }
12010
12011 if (temp_dc.IsOk()) {
12012 // Arrange to render the World Chart vector data behind the rendered
12013 // current chart so that uncovered canvas areas show at least the world
12014 // chart.
12015 OCPNRegion chartValidRegion;
12016 if (!VPoint.b_quilt) {
12017 // Make a region covering the current chart on the canvas
12018
12019 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12020 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12021 else {
12022 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12023 // require that the viewport passed here have pix_width and pix_height
12024 // set to the actual display, not the virtual (rv_rect) sizes
12025 // (the vector calculations require the virtual sizes in svp)
12026
12027 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12028 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12029 }
12030 } else
12031 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12032
12033 temp_dc.DestroyClippingRegion();
12034
12035 // Copy current chart region
12036 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12037
12038 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12039
12040 if (!backgroundRegion.IsEmpty()) {
12041 // Draw the Background Chart only in the areas NOT covered by the
12042 // current chart view
12043
12044 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12045 clipping regions with more than 1 rectangle so... */
12046 wxColour water = pWorldBackgroundChart->water;
12047 if (water.IsOk()) {
12048 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12049 temp_dc.SetBrush(wxBrush(water));
12050 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12051 while (upd.HaveRects()) {
12052 wxRect rect = upd.GetRect();
12053 temp_dc.DrawRectangle(rect);
12054 upd.NextRect();
12055 }
12056 }
12057 // Associate with temp_dc
12058 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12059 temp_dc.SetDeviceClippingRegion(*clip_region);
12060 delete clip_region;
12061
12062 ocpnDC bgdc(temp_dc);
12063 double r = VPoint.rotation;
12064 SetVPRotation(VPoint.skew);
12065
12066 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12067 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12068
12069 SetVPRotation(r);
12070 }
12071 } // temp_dc.IsOk();
12072
12073 wxMemoryDC *pChartDC = &temp_dc;
12074 wxMemoryDC rotd_dc;
12075
12076 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12077 // Can we use the current rotated image cache?
12078 if (!b_rcache_ok) {
12079#ifdef __WXMSW__
12080 wxMemoryDC tbase_dc;
12081 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12082 tbase_dc.SelectObject(bm_base);
12083 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12084 tbase_dc.SelectObject(wxNullBitmap);
12085#else
12086 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12087#endif
12088
12089 wxImage base_image;
12090 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12091
12092 // Use a local static image rotator to improve wxWidgets code profile
12093 // Especially, on GTK the wxRound and wxRealPoint functions are very
12094 // expensive.....
12095
12096 double angle = GetVP().skew - GetVP().rotation;
12097 wxImage ri;
12098 bool b_rot_ok = false;
12099 if (base_image.IsOk()) {
12100 ViewPort rot_vp = GetVP();
12101
12102 m_b_rot_hidef = false;
12103
12104 ri = Image_Rotate(
12105 base_image, angle,
12106 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12107 m_b_rot_hidef, &m_roffset);
12108
12109 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12110 (rot_vp.rotation == VPoint.rotation) &&
12111 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12112 rot_vp.IsValid() && (ri.IsOk())) {
12113 b_rot_ok = true;
12114 }
12115 }
12116
12117 if (b_rot_ok) {
12118 delete m_prot_bm;
12119 m_prot_bm = new wxBitmap(ri);
12120 }
12121
12122 m_roffset.x += VPoint.rv_rect.x;
12123 m_roffset.y += VPoint.rv_rect.y;
12124 }
12125
12126 if (m_prot_bm && m_prot_bm->IsOk()) {
12127 rotd_dc.SelectObject(*m_prot_bm);
12128 pChartDC = &rotd_dc;
12129 } else {
12130 pChartDC = &temp_dc;
12131 m_roffset = wxPoint(0, 0);
12132 }
12133 } else { // unrotated
12134 pChartDC = &temp_dc;
12135 m_roffset = wxPoint(0, 0);
12136 }
12137
12138 wxPoint offset = m_roffset;
12139
12140 // Save the PixelCache viewpoint for next time
12141 m_cache_vp = VPoint;
12142
12143 // Set up a scratch DC for overlay objects
12144 wxMemoryDC mscratch_dc;
12145 mscratch_dc.SelectObject(*pscratch_bm);
12146
12147 mscratch_dc.ResetBoundingBox();
12148 mscratch_dc.DestroyClippingRegion();
12149 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12150
12151 // Blit the externally invalidated areas of the chart onto the scratch dc
12152 wxRegionIterator upd(rgn_blit); // get the update rect list
12153 while (upd) {
12154 wxRect rect = upd.GetRect();
12155
12156 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12157 rect.x - offset.x, rect.y - offset.y);
12158 upd++;
12159 }
12160
12161 // If multi-canvas, indicate which canvas has keyboard focus
12162 // by drawing a simple blue bar at the top.
12163 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12164 if (this == wxWindow::FindFocus()) {
12165 g_focusCanvas = this;
12166
12167 wxColour colour = GetGlobalColor("BLUE4");
12168 mscratch_dc.SetPen(wxPen(colour));
12169 mscratch_dc.SetBrush(wxBrush(colour));
12170
12171 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12172 mscratch_dc.DrawRectangle(activeRect);
12173 }
12174 }
12175
12176 // Any MBtiles?
12177 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12178 unsigned int im = stackIndexArray.size();
12179 if (VPoint.b_quilt && im > 0) {
12180 std::vector<int> tiles_to_show;
12181 for (unsigned int is = 0; is < im; is++) {
12182 const ChartTableEntry &cte =
12183 ChartData->GetChartTableEntry(stackIndexArray[is]);
12184 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12185 continue;
12186 }
12187 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12188 tiles_to_show.push_back(stackIndexArray[is]);
12189 }
12190 }
12191
12192 if (tiles_to_show.size())
12193 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12194 }
12195
12196 // May get an unexpected OnPaint call while switching display modes
12197 // Guard for that.
12198 if (!g_bopengl) {
12199 ocpnDC scratch_dc(mscratch_dc);
12200 RenderAlertMessage(mscratch_dc, GetVP());
12201 }
12202
12203#if 0
12204 // quiting?
12205 if (g_bquiting) {
12206#ifdef ocpnUSE_DIBSECTION
12207 ocpnMemDC q_dc;
12208#else
12209 wxMemoryDC q_dc;
12210#endif
12211 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12212 q_dc.SelectObject(qbm);
12213
12214 // Get a copy of the screen
12215 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12216
12217 // Draw a rectangle over the screen with a stipple brush
12218 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12219 q_dc.SetBrush(qbr);
12220 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12221
12222 // Blit back into source
12223 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12224 wxCOPY);
12225
12226 q_dc.SelectObject(wxNullBitmap);
12227 }
12228#endif
12229
12230#if 0
12231 // It is possible that this two-step method may be reuired for some platforms.
12232 // So, retain in the code base to aid recovery if necessary
12233
12234 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12235 if( VPoint.b_quilt ) {
12236 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12237 ChartBase *chart = m_pQuilt->GetRefChart();
12238 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12239
12240 // Clear the text Global declutter list
12241 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12242 if(ChPI)
12243 ChPI->ClearPLIBTextList();
12244 else{
12245 if(ps52plib)
12246 ps52plib->ClearTextList();
12247 }
12248
12249 wxMemoryDC t_dc;
12250 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12251
12252 wxColor maskBackground = wxColour(1,0,0);
12253 t_dc.SelectObject( qbm );
12254 t_dc.SetBackground(wxBrush(maskBackground));
12255 t_dc.Clear();
12256
12257 // Copy the scratch DC into the new bitmap
12258 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12259
12260 // Render the text to the new bitmap
12261 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12262 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12263
12264 // Copy the new bitmap back to the scratch dc
12265 wxRegionIterator upd_final( ru );
12266 while( upd_final ) {
12267 wxRect rect = upd_final.GetRect();
12268 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12269 upd_final++;
12270 }
12271
12272 t_dc.SelectObject( wxNullBitmap );
12273 }
12274 }
12275 }
12276#endif
12277 // Direct rendering model...
12278 if (VPoint.b_quilt) {
12279 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12280 ChartBase *chart = m_pQuilt->GetRefChart();
12281 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12282 // Clear the text Global declutter list
12283 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12284 if (ChPI)
12285 ChPI->ClearPLIBTextList();
12286 else {
12287 if (ps52plib) ps52plib->ClearTextList();
12288 }
12289
12290 // Render the text directly to the scratch bitmap
12291 OCPNRegion chart_all_text_region(
12292 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12293
12294 if (g_bShowChartBar && m_Piano) {
12295 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12296 GetVP().pix_width, m_Piano->GetHeight());
12297
12298 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12299 if (!style->chartStatusWindowTransparent)
12300 chart_all_text_region.Subtract(chart_bar_rect);
12301 }
12302
12303 if (m_Compass && m_Compass->IsShown()) {
12304 wxRect compassRect = m_Compass->GetRect();
12305 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12306 chart_all_text_region.Subtract(compassRect);
12307 }
12308 }
12309
12310 mscratch_dc.DestroyClippingRegion();
12311
12312 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12313 chart_all_text_region);
12314 }
12315 }
12316 }
12317
12318 // Now that charts are fully rendered, apply the overlay objects as decals.
12319 ocpnDC scratch_dc(mscratch_dc);
12320 DrawOverlayObjects(scratch_dc, ru);
12321
12322 // And finally, blit the scratch dc onto the physical dc
12323 wxRegionIterator upd_final(rgn_blit);
12324 while (upd_final) {
12325 wxRect rect = upd_final.GetRect();
12326 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12327 rect.y);
12328 upd_final++;
12329 }
12330
12331 // Deselect the chart bitmap from the temp_dc, so that it will not be
12332 // destroyed in the temp_dc dtor
12333 temp_dc.SelectObject(wxNullBitmap);
12334 // And for the scratch bitmap
12335 mscratch_dc.SelectObject(wxNullBitmap);
12336
12337 dc.DestroyClippingRegion();
12338
12339 PaintCleanup();
12340}
12341
12342void ChartCanvas::PaintCleanup() {
12343 // Handle the current graphic window, if present
12344 if (m_inPinch) return;
12345
12346 if (pCwin) {
12347 pCwin->Show();
12348 if (m_bTCupdate) {
12349 pCwin->Refresh();
12350 pCwin->Update();
12351 }
12352 }
12353
12354 // And set flags for next time
12355 m_bTCupdate = false;
12356
12357 // Handle deferred WarpPointer
12358 if (warp_flag) {
12359 WarpPointer(warp_x, warp_y);
12360 warp_flag = false;
12361 }
12362
12363 // Start movement timers, this runs nearly immediately.
12364 // the reason we cannot simply call it directly is the
12365 // refresh events it emits may be blocked from this paint event
12366 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12367 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12368}
12369
12370#if 0
12371wxColour GetErrorGraphicColor(double val)
12372{
12373 /*
12374 double valm = wxMin(val_max, val);
12375
12376 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12377 unsigned char red = (unsigned char)(255 * (valm/val_max));
12378
12379 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12380
12381 hv.saturation = 1.0;
12382 hv.value = 1.0;
12383
12384 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12385 return wxColour(rv.red, rv.green, rv.blue);
12386 */
12387
12388 // HTML colors taken from NOAA WW3 Web representation
12389 wxColour c;
12390 if((val > 0) && (val < 1)) c.Set("#002ad9");
12391 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12392 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12393 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12394 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12395 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12396 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12397 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12398 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12399 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12400 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12401 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12402 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12403 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12404 else if((val >= 30) && (val < 36)) c.Set("#870000");
12405 else if((val >= 36) && (val < 42)) c.Set("#690000");
12406 else if((val >= 42) && (val < 48)) c.Set("#550000");
12407 else if( val >= 48) c.Set("#410000");
12408
12409 return c;
12410}
12411
12412void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12413{
12414 wxImage gr_image(vp->pix_width, vp->pix_height);
12415 gr_image.InitAlpha();
12416
12417 double maxval = -10000;
12418 double minval = 10000;
12419
12420 double rlat, rlon;
12421 double glat, glon;
12422
12423 GetCanvasPixPoint(0, 0, rlat, rlon);
12424
12425 for(int i=1; i < vp->pix_height-1; i++)
12426 {
12427 for(int j=0; j < vp->pix_width; j++)
12428 {
12429 // Reference mercator value
12430// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12431
12432 // Georef value
12433 GetCanvasPixPoint(j, i, glat, glon);
12434
12435 maxval = wxMax(maxval, (glat - rlat));
12436 minval = wxMin(minval, (glat - rlat));
12437
12438 }
12439 rlat = glat;
12440 }
12441
12442 GetCanvasPixPoint(0, 0, rlat, rlon);
12443 for(int i=1; i < vp->pix_height-1; i++)
12444 {
12445 for(int j=0; j < vp->pix_width; j++)
12446 {
12447 // Reference mercator value
12448// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12449
12450 // Georef value
12451 GetCanvasPixPoint(j, i, glat, glon);
12452
12453 double f = ((glat - rlat)-minval)/(maxval - minval);
12454
12455 double dy = (f * 40);
12456
12457 wxColour c = GetErrorGraphicColor(dy);
12458 unsigned char r = c.Red();
12459 unsigned char g = c.Green();
12460 unsigned char b = c.Blue();
12461
12462 gr_image.SetRGB(j, i, r,g,b);
12463 if((glat - rlat )!= 0)
12464 gr_image.SetAlpha(j, i, 128);
12465 else
12466 gr_image.SetAlpha(j, i, 255);
12467
12468 }
12469 rlat = glat;
12470 }
12471
12472 // Create a Bitmap
12473 wxBitmap *pbm = new wxBitmap(gr_image);
12474 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12475 pbm->SetMask(gr_mask);
12476
12477 pmdc->DrawBitmap(*pbm, 0,0);
12478
12479 delete pbm;
12480
12481}
12482
12483#endif
12484
12485void ChartCanvas::CancelMouseRoute() {
12486 m_routeState = 0;
12487 m_pMouseRoute = NULL;
12488 m_bDrawingRoute = false;
12489}
12490
12491int ChartCanvas::GetNextContextMenuId() {
12492 return CanvasMenuHandler::GetNextContextMenuId();
12493}
12494
12495bool ChartCanvas::SetCursor(const wxCursor &c) {
12496#ifdef ocpnUSE_GL
12497 if (g_bopengl && m_glcc)
12498 return m_glcc->SetCursor(c);
12499 else
12500#endif
12501 return wxWindow::SetCursor(c);
12502}
12503
12504void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12505 if (g_bquiting) return;
12506 // Keep the mouse position members up to date
12507 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12508
12509 // Retrigger the route leg popup timer
12510 // This handles the case when the chart is moving in auto-follow mode,
12511 // but no user mouse input is made. The timer handler may Hide() the
12512 // popup if the chart moved enough n.b. We use slightly longer oneshot
12513 // value to allow this method's Refresh() to complete before potentially
12514 // getting another Refresh() in the popup timer handler.
12515 if (!m_RolloverPopupTimer.IsRunning() &&
12516 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12517 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12518 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12519 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12520
12521#ifdef ocpnUSE_GL
12522 if (m_glcc && g_bopengl) {
12523 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12524 // overlay objects.
12525 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12526
12527 m_glcc->Refresh(eraseBackground,
12528 NULL); // We always are going to render the entire screen
12529 // anyway, so make
12530 // sure that the window managers understand the invalid area
12531 // is actually the entire client area.
12532
12533 // We need to selectively Refresh some child windows, if they are visible.
12534 // Note that some children are refreshed elsewhere on timer ticks, so don't
12535 // need attention here.
12536
12537 // Thumbnail chart
12538 if (pthumbwin && pthumbwin->IsShown()) {
12539 pthumbwin->Raise();
12540 pthumbwin->Refresh(false);
12541 }
12542
12543 // ChartInfo window
12544 if (m_pCIWin && m_pCIWin->IsShown()) {
12545 m_pCIWin->Raise();
12546 m_pCIWin->Refresh(false);
12547 }
12548
12549 // if(g_MainToolbar)
12550 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12551
12552 } else
12553#endif
12554 wxWindow::Refresh(eraseBackground, rect);
12555}
12556
12557void ChartCanvas::Update() {
12558 if (m_glcc && g_bopengl) {
12559#ifdef ocpnUSE_GL
12560 m_glcc->Update();
12561#endif
12562 } else
12563 wxWindow::Update();
12564}
12565
12566void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12567 if (!pemboss) return;
12568 int x = pemboss->x, y = pemboss->y;
12569 const double factor = 200;
12570
12571 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12572 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12573 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12574
12575 // Grab a snipped image out of the chart
12576 wxMemoryDC snip_dc;
12577 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12578 snip_dc.SelectObject(snip_bmp);
12579
12580 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12581 snip_dc.SelectObject(wxNullBitmap);
12582
12583 wxImage snip_img = snip_bmp.ConvertToImage();
12584
12585 // Apply Emboss map to the snip image
12586 unsigned char *pdata = snip_img.GetData();
12587 if (pdata) {
12588 for (int y = 0; y < pemboss->height; y++) {
12589 int map_index = (y * pemboss->width);
12590 for (int x = 0; x < pemboss->width; x++) {
12591 double val = (pemboss->pmap[map_index] * factor) / 256.;
12592
12593 int nred = (int)((*pdata) + val);
12594 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12595 *pdata++ = (unsigned char)nred;
12596
12597 int ngreen = (int)((*pdata) + val);
12598 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12599 *pdata++ = (unsigned char)ngreen;
12600
12601 int nblue = (int)((*pdata) + val);
12602 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12603 *pdata++ = (unsigned char)nblue;
12604
12605 map_index++;
12606 }
12607 }
12608 }
12609
12610 // Convert embossed snip to a bitmap
12611 wxBitmap emb_bmp(snip_img);
12612
12613 // Map to another memoryDC
12614 wxMemoryDC result_dc;
12615 result_dc.SelectObject(emb_bmp);
12616
12617 // Blit to target
12618 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12619
12620 result_dc.SelectObject(wxNullBitmap);
12621}
12622
12623emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12624 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12625
12626 if (GetQuiltMode()) {
12627 // disable Overzoom indicator for MBTiles
12628 int refIndex = GetQuiltRefChartdbIndex();
12629 if (refIndex >= 0) {
12630 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12631 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12632 if (current_type == CHART_TYPE_MBTILES) {
12633 ChartBase *pChart = m_pQuilt->GetRefChart();
12634 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12635 if (ptc) {
12636 zoom_factor = ptc->GetZoomFactor();
12637 }
12638 }
12639 }
12640
12641 if (zoom_factor <= 3.9) return NULL;
12642 } else {
12643 if (m_singleChart) {
12644 if (zoom_factor <= 3.9) return NULL;
12645 } else
12646 return NULL;
12647 }
12648
12649 if (m_pEM_OverZoom) {
12650 m_pEM_OverZoom->x = 4;
12651 m_pEM_OverZoom->y = 0;
12652 if (g_MainToolbar && IsPrimaryCanvas()) {
12653 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12654 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12655 }
12656 }
12657 return m_pEM_OverZoom;
12658}
12659
12660void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12661 GridDraw(dc);
12662
12663 // bool pluginOverlayRender = true;
12664 //
12665 // if(g_canvasConfig > 0){ // Multi canvas
12666 // if(IsPrimaryCanvas())
12667 // pluginOverlayRender = false;
12668 // }
12669
12670 g_overlayCanvas = this;
12671
12672 if (g_pi_manager) {
12673 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12674 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12676 }
12677
12678 AISDrawAreaNotices(dc, GetVP(), this);
12679
12680 wxDC *pdc = dc.GetDC();
12681 if (pdc) {
12682 pdc->DestroyClippingRegion();
12683 wxDCClipper(*pdc, ru);
12684 }
12685
12686 if (m_bShowNavobjects) {
12687 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12688 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12689 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12690 DrawAnchorWatchPoints(dc);
12691 } else {
12692 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12693 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12694 }
12695
12696 AISDraw(dc, GetVP(), this);
12697 ShipDraw(dc);
12698 AlertDraw(dc);
12699
12700 RenderVisibleSectorLights(dc);
12701
12702 RenderAllChartOutlines(dc, GetVP());
12703 RenderRouteLegs(dc);
12704 RenderShipToActive(dc, false);
12705 ScaleBarDraw(dc);
12706 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12707 if (g_pi_manager) {
12708 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12710 }
12711
12712 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12713 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12714
12715 if (g_pi_manager) {
12716 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12718 }
12719
12720 if (m_bShowTide) {
12721 RebuildTideSelectList(GetVP().GetBBox());
12722 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12723 }
12724
12725 if (m_bShowCurrent) {
12726 RebuildCurrentSelectList(GetVP().GetBBox());
12727 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12728 }
12729
12730 if (!g_PrintingInProgress) {
12731 if (IsPrimaryCanvas()) {
12732 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12733 }
12734
12735 if (IsPrimaryCanvas()) {
12736 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12737 }
12738
12739 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12740
12741 if (m_pTrackRolloverWin) {
12742 m_pTrackRolloverWin->Draw(dc);
12743 m_brepaint_piano = true;
12744 }
12745
12746 if (m_pRouteRolloverWin) {
12747 m_pRouteRolloverWin->Draw(dc);
12748 m_brepaint_piano = true;
12749 }
12750
12751 if (m_pAISRolloverWin) {
12752 m_pAISRolloverWin->Draw(dc);
12753 m_brepaint_piano = true;
12754 }
12755 if (m_brepaint_piano && g_bShowChartBar) {
12756 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12757 }
12758
12759 if (m_Compass) m_Compass->Paint(dc);
12760
12761 if (!g_CanvasHideNotificationIcon) {
12762 if (IsPrimaryCanvas()) {
12763 auto &noteman = NotificationManager::GetInstance();
12764 if (noteman.GetNotificationCount()) {
12765 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12766 if (m_notification_button->UpdateStatus()) Refresh();
12767 m_notification_button->Show(true);
12768 m_notification_button->Paint(dc);
12769 } else {
12770 m_notification_button->Show(false);
12771 }
12772 }
12773 }
12774 }
12775 if (g_pi_manager) {
12776 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12778 }
12779}
12780
12781emboss_data *ChartCanvas::EmbossDepthScale() {
12782 if (!m_bShowDepthUnits) return NULL;
12783
12784 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12785
12786 if (GetQuiltMode()) {
12787 wxString s = m_pQuilt->GetQuiltDepthUnit();
12788 s.MakeUpper();
12789 if (s == "FEET")
12790 depth_unit_type = DEPTH_UNIT_FEET;
12791 else if (s.StartsWith("FATHOMS"))
12792 depth_unit_type = DEPTH_UNIT_FATHOMS;
12793 else if (s.StartsWith("METERS"))
12794 depth_unit_type = DEPTH_UNIT_METERS;
12795 else if (s.StartsWith("METRES"))
12796 depth_unit_type = DEPTH_UNIT_METERS;
12797 else if (s.StartsWith("METRIC"))
12798 depth_unit_type = DEPTH_UNIT_METERS;
12799 else if (s.StartsWith("METER"))
12800 depth_unit_type = DEPTH_UNIT_METERS;
12801
12802 } else {
12803 if (m_singleChart) {
12804 depth_unit_type = m_singleChart->GetDepthUnitType();
12805 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12806 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12807 }
12808 }
12809
12810 emboss_data *ped = NULL;
12811 switch (depth_unit_type) {
12812 case DEPTH_UNIT_FEET:
12813 ped = m_pEM_Feet;
12814 break;
12815 case DEPTH_UNIT_METERS:
12816 ped = m_pEM_Meters;
12817 break;
12818 case DEPTH_UNIT_FATHOMS:
12819 ped = m_pEM_Fathoms;
12820 break;
12821 default:
12822 return NULL;
12823 }
12824
12825 ped->x = (GetVP().pix_width - ped->width);
12826
12827 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12828 wxRect r = m_Compass->GetRect();
12829 ped->y = r.y + r.height;
12830 } else {
12831 ped->y = 40;
12832 }
12833 return ped;
12834}
12835
12836void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12837 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12838 wxFont font;
12839 if (style->embossFont == wxEmptyString) {
12840 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12841 font = *dFont;
12842 font.SetPointSize(60);
12843 font.SetWeight(wxFONTWEIGHT_BOLD);
12844 } else
12845 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12846 wxFONTWEIGHT_BOLD, false, style->embossFont);
12847
12848 int emboss_width = 500;
12849 int emboss_height = 200;
12850
12851 // Free any existing emboss maps
12852 delete m_pEM_Feet;
12853 delete m_pEM_Meters;
12854 delete m_pEM_Fathoms;
12855
12856 // Create the 3 DepthUnit emboss map structures
12857 m_pEM_Feet =
12858 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12859 m_pEM_Meters =
12860 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12861 m_pEM_Fathoms =
12862 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12863}
12864
12865#define OVERZOOM_TEXT _("OverZoom")
12866
12867void ChartCanvas::SetOverzoomFont() {
12868 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12869 int w, h;
12870
12871 wxFont font;
12872 if (style->embossFont == wxEmptyString) {
12873 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12874 font = *dFont;
12875 font.SetPointSize(40);
12876 font.SetWeight(wxFONTWEIGHT_BOLD);
12877 } else
12878 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12879 wxFONTWEIGHT_BOLD, false, style->embossFont);
12880
12881 wxClientDC dc(this);
12882 dc.SetFont(font);
12883 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12884
12885 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12886 font.SetPointSize(font.GetPointSize() - 1);
12887 dc.SetFont(font);
12888 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12889 }
12890 m_overzoomFont = font;
12891 m_overzoomTextWidth = w;
12892 m_overzoomTextHeight = h;
12893}
12894
12895void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12896 delete m_pEM_OverZoom;
12897
12898 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12899 m_pEM_OverZoom =
12900 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12901 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12902}
12903
12904emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12905 int height, const wxString &str,
12906 ColorScheme cs) {
12907 int *pmap;
12908
12909 // Create a temporary bitmap
12910 wxBitmap bmp(width, height, -1);
12911
12912 // Create a memory DC
12913 wxMemoryDC temp_dc;
12914 temp_dc.SelectObject(bmp);
12915
12916 // Paint on it
12917 temp_dc.SetBackground(*wxWHITE_BRUSH);
12918 temp_dc.SetTextBackground(*wxWHITE);
12919 temp_dc.SetTextForeground(*wxBLACK);
12920
12921 temp_dc.Clear();
12922
12923 temp_dc.SetFont(font);
12924
12925 int str_w, str_h;
12926 temp_dc.GetTextExtent(str, &str_w, &str_h);
12927 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12928 temp_dc.DrawText(str, 1, 1);
12929
12930 // Deselect the bitmap
12931 temp_dc.SelectObject(wxNullBitmap);
12932
12933 // Convert bitmap the wxImage for manipulation
12934 wxImage img = bmp.ConvertToImage();
12935
12936 int image_width = str_w * 105 / 100;
12937 int image_height = str_h * 105 / 100;
12938 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12939 wxMin(image_height, img.GetHeight()));
12940 wxImage imgs = img.GetSubImage(r);
12941
12942 double val_factor;
12943 switch (cs) {
12944 case GLOBAL_COLOR_SCHEME_DAY:
12945 default:
12946 val_factor = 1;
12947 break;
12948 case GLOBAL_COLOR_SCHEME_DUSK:
12949 val_factor = .5;
12950 break;
12951 case GLOBAL_COLOR_SCHEME_NIGHT:
12952 val_factor = .25;
12953 break;
12954 }
12955
12956 int val;
12957 int index;
12958 const int w = imgs.GetWidth();
12959 const int h = imgs.GetHeight();
12960 pmap = (int *)calloc(w * h * sizeof(int), 1);
12961 // Create emboss map by differentiating the emboss image
12962 // and storing integer results in pmap
12963 // n.b. since the image is B/W, it is sufficient to check
12964 // one channel (i.e. red) only
12965 for (int y = 1; y < h - 1; y++) {
12966 for (int x = 1; x < w - 1; x++) {
12967 val =
12968 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
12969 val = (int)(val * val_factor);
12970 index = (y * w) + x;
12971 pmap[index] = val;
12972 }
12973 }
12974
12975 emboss_data *pret = new emboss_data;
12976 pret->pmap = pmap;
12977 pret->width = w;
12978 pret->height = h;
12979
12980 return pret;
12981}
12982
12983void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12984 Track *active_track = NULL;
12985 for (Track *pTrackDraw : g_TrackList) {
12986 if (g_pActiveTrack == pTrackDraw) {
12987 active_track = pTrackDraw;
12988 continue;
12989 }
12990
12991 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
12992 }
12993
12994 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12995}
12996
12997void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12998 Track *active_track = NULL;
12999 for (Track *pTrackDraw : g_TrackList) {
13000 if (g_pActiveTrack == pTrackDraw) {
13001 active_track = pTrackDraw;
13002 break;
13003 }
13004 }
13005 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13006}
13007
13008void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13009 Route *active_route = NULL;
13010 for (Route *pRouteDraw : *pRouteList) {
13011 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13012 active_route = pRouteDraw;
13013 continue;
13014 }
13015
13016 // if(m_canvasIndex == 1)
13017 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13018 }
13019
13020 // Draw any active or selected route (or track) last, so that is is always on
13021 // top
13022 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13023}
13024
13025void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13026 Route *active_route = NULL;
13027
13028 for (Route *pRouteDraw : *pRouteList) {
13029 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13030 active_route = pRouteDraw;
13031 break;
13032 }
13033 }
13034 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13035}
13036
13037void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13038 if (!pWayPointMan) return;
13039
13040 auto node = pWayPointMan->GetWaypointList()->begin();
13041
13042 while (node != pWayPointMan->GetWaypointList()->end()) {
13043 RoutePoint *pWP = *node;
13044 if (pWP) {
13045 if (pWP->m_bIsInRoute) {
13046 ++node;
13047 continue;
13048 }
13049
13050 /* technically incorrect... waypoint has bounding box */
13051 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13052 RoutePointGui(*pWP).Draw(dc, this, NULL);
13053 else {
13054 // Are Range Rings enabled?
13055 if (pWP->GetShowWaypointRangeRings() &&
13056 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13057 double factor = 1.00;
13058 if (pWP->GetWaypointRangeRingsStepUnits() ==
13059 1) // convert kilometers to NMi
13060 factor = 1 / 1.852;
13061
13062 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13063 pWP->GetWaypointRangeRingsStep() / 60.;
13064 radius *= 2; // Fudge factor
13065
13066 LLBBox radar_box;
13067 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13068 pWP->m_lat + radius, pWP->m_lon + radius);
13069 if (!BltBBox.IntersectOut(radar_box)) {
13070 RoutePointGui(*pWP).Draw(dc, this, NULL);
13071 }
13072 }
13073 }
13074 }
13075
13076 ++node;
13077 }
13078}
13079
13080void ChartCanvas::DrawBlinkObjects() {
13081 // All RoutePoints
13082 wxRect update_rect;
13083
13084 if (!pWayPointMan) return;
13085
13086 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13087 if (pWP) {
13088 if (pWP->m_bBlink) {
13089 update_rect.Union(pWP->CurrentRect_in_DC);
13090 }
13091 }
13092 }
13093 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13094}
13095
13096void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13097 // draw anchor watch rings, if activated
13098
13100 wxPoint r1, r2;
13101 wxPoint lAnchorPoint1, lAnchorPoint2;
13102 double lpp1 = 0.0;
13103 double lpp2 = 0.0;
13104 if (pAnchorWatchPoint1) {
13105 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13107 &lAnchorPoint1);
13108 }
13109 if (pAnchorWatchPoint2) {
13110 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13112 &lAnchorPoint2);
13113 }
13114
13115 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13116 wxPen ppPenr(GetGlobalColor("URED"), 2);
13117
13118 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13119 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13120 dc.SetBrush(*ppBrush);
13121
13122 if (lpp1 > 0) {
13123 dc.SetPen(ppPeng);
13124 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13125 }
13126
13127 if (lpp2 > 0) {
13128 dc.SetPen(ppPeng);
13129 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13130 }
13131
13132 if (lpp1 < 0) {
13133 dc.SetPen(ppPenr);
13134 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13135 }
13136
13137 if (lpp2 < 0) {
13138 dc.SetPen(ppPenr);
13139 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13140 }
13141 }
13142}
13143
13144double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13145 double lpp = 0.;
13146 wxPoint r1;
13147 wxPoint lAnchorPoint;
13148 double d1 = 0.0;
13149 double dabs;
13150 double tlat1, tlon1;
13151
13152 if (pAnchorWatchPoint) {
13153 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13154 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13155 dabs = fabs(d1 / 1852.);
13156 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13157 &tlat1, &tlon1);
13158 GetCanvasPointPix(tlat1, tlon1, &r1);
13159 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13160 &lAnchorPoint);
13161 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13162 pow((double)(lAnchorPoint.y - r1.y), 2));
13163
13164 // This is an entry watch
13165 if (d1 < 0) lpp = -lpp;
13166 }
13167 return lpp;
13168}
13169
13170//------------------------------------------------------------------------------------------
13171// Tides Support
13172//------------------------------------------------------------------------------------------
13173void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13174 if (!ptcmgr) return;
13175
13176 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13177
13178 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13179 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13180 double lon = pIDX->IDX_lon;
13181 double lat = pIDX->IDX_lat;
13182
13183 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13184 if ((type == 't') || (type == 'T')) {
13185 if (BBox.Contains(lat, lon)) {
13186 // Manage the point selection list
13187 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13188 }
13189 }
13190 }
13191}
13192
13193void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13194 if (!ptcmgr) return;
13195
13196 wxDateTime this_now = gTimeSource;
13197 bool cur_time = !gTimeSource.IsValid();
13198 if (cur_time) this_now = wxDateTime::Now();
13199 time_t t_this_now = this_now.GetTicks();
13200
13201 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13202 wxPENSTYLE_SOLID);
13203 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13204 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13205 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13206 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13207
13208 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13209 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13210 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13211 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13212 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13213 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13214
13215 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13216 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13217 int font_size = wxMax(10, dFont->GetPointSize());
13218 font_size /= g_Platform->GetDisplayDIPMult(this);
13219 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13220 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13221 false, dFont->GetFaceName());
13222
13223 dc.SetPen(*pblack_pen);
13224 dc.SetBrush(*pgreen_brush);
13225
13226 wxBitmap bm;
13227 switch (m_cs) {
13228 case GLOBAL_COLOR_SCHEME_DAY:
13229 bm = m_bmTideDay;
13230 break;
13231 case GLOBAL_COLOR_SCHEME_DUSK:
13232 bm = m_bmTideDusk;
13233 break;
13234 case GLOBAL_COLOR_SCHEME_NIGHT:
13235 bm = m_bmTideNight;
13236 break;
13237 default:
13238 bm = m_bmTideDay;
13239 break;
13240 }
13241
13242 int bmw = bm.GetWidth();
13243 int bmh = bm.GetHeight();
13244
13245 float scale_factor = 1.0;
13246
13247 // Set the onscreen size of the symbol
13248 // Compensate for various display resolutions
13249 float icon_pixelRefDim = 45;
13250
13251 // Tidal report graphic is scaled by the text size of the label in use
13252 wxScreenDC sdc;
13253 int height;
13254 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13255 height *= g_Platform->GetDisplayDIPMult(this);
13256 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13257
13258 scale_factor *= pix_factor;
13259
13260 float user_scale_factor = g_ChartScaleFactorExp;
13261 if (g_ChartScaleFactorExp > 1.0)
13262 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13263 1.2; // soften the scale factor a bit
13264
13265 scale_factor *= user_scale_factor;
13266 scale_factor *= GetContentScaleFactor();
13267
13268 {
13269 double marge = 0.05;
13270 std::vector<LLBBox> drawn_boxes;
13271 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13272 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13273
13274 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13275 if ((type == 't') || (type == 'T')) // only Tides
13276 {
13277 double lon = pIDX->IDX_lon;
13278 double lat = pIDX->IDX_lat;
13279
13280 if (BBox.ContainsMarge(lat, lon, marge)) {
13281 // Avoid drawing detailed graphic for duplicate tide stations
13282 if (GetVP().chart_scale < 500000) {
13283 bool bdrawn = false;
13284 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13285 if (drawn_boxes[i].Contains(lat, lon)) {
13286 bdrawn = true;
13287 break;
13288 }
13289 }
13290 if (bdrawn) continue; // the station loop
13291
13292 LLBBox this_box;
13293 this_box.Set(lat, lon, lat, lon);
13294 this_box.EnLarge(.005);
13295 drawn_boxes.push_back(this_box);
13296 }
13297
13298 wxPoint r;
13299 GetCanvasPointPix(lat, lon, &r);
13300 // draw standard icons
13301 if (GetVP().chart_scale > 500000) {
13302 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13303 }
13304 // draw "extended" icons
13305 else {
13306 dc.SetFont(*plabelFont);
13307 {
13308 {
13309 float val, nowlev;
13310 float ltleve = 0.;
13311 float htleve = 0.;
13312 time_t tctime;
13313 time_t lttime = 0;
13314 time_t httime = 0;
13315 bool wt;
13316 // define if flood or ebb in the last ten minutes and verify if
13317 // data are useable
13318 if (ptcmgr->GetTideFlowSens(
13319 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13320 pIDX->IDX_rec_num, nowlev, val, wt)) {
13321 // search forward the first HW or LW near "now" ( starting at
13322 // "now" - ten minutes )
13323 ptcmgr->GetHightOrLowTide(
13324 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13325 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13326 wt, pIDX->IDX_rec_num, val, tctime);
13327 if (wt) {
13328 httime = tctime;
13329 htleve = val;
13330 } else {
13331 lttime = tctime;
13332 ltleve = val;
13333 }
13334 wt = !wt;
13335
13336 // then search opposite tide near "now"
13337 if (tctime > t_this_now) // search backward
13338 ptcmgr->GetHightOrLowTide(
13339 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13340 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13341 pIDX->IDX_rec_num, val, tctime);
13342 else
13343 // or search forward
13344 ptcmgr->GetHightOrLowTide(
13345 t_this_now, FORWARD_TEN_MINUTES_STEP,
13346 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13347 val, tctime);
13348 if (wt) {
13349 httime = tctime;
13350 htleve = val;
13351 } else {
13352 lttime = tctime;
13353 ltleve = val;
13354 }
13355
13356 // draw the tide rectangle:
13357
13358 // tide icon rectangle has default pre-scaled width = 12 ,
13359 // height = 45
13360 int width = (int)(12 * scale_factor + 0.5);
13361 int height = (int)(45 * scale_factor + 0.5);
13362 int linew = wxMax(1, (int)(scale_factor));
13363 int xDraw = r.x - (width / 2);
13364 int yDraw = r.y - (height / 2);
13365
13366 // process tide state ( %height and flow sens )
13367 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13368 int hs = (httime > lttime) ? -4 : 4;
13369 hs *= (int)(scale_factor + 0.5);
13370 if (ts > 0.995 || ts < 0.005) hs = 0;
13371 int ht_y = (int)(height * ts);
13372
13373 // draw yellow tide rectangle outlined in black
13374 pblack_pen->SetWidth(linew);
13375 dc.SetPen(*pblack_pen);
13376 dc.SetBrush(*pyelo_brush);
13377 dc.DrawRectangle(xDraw, yDraw, width, height);
13378
13379 // draw blue rectangle as water height, smaller in width than
13380 // yellow rectangle
13381 dc.SetPen(*pblue_pen);
13382 dc.SetBrush(*pblue_brush);
13383 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13384 (width - (4 * linew)), height - ht_y);
13385
13386 // draw sens arrows (ensure they are not "under-drawn" by top
13387 // line of blue rectangle )
13388 int hl;
13389 wxPoint arrow[3];
13390 arrow[0].x = xDraw + 2 * linew;
13391 arrow[1].x = xDraw + width / 2;
13392 arrow[2].x = xDraw + width - 2 * linew;
13393 pyelo_pen->SetWidth(linew);
13394 pblue_pen->SetWidth(linew);
13395 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13396 {
13397 hl = (int)(height * 0.25) + yDraw;
13398 arrow[0].y = hl;
13399 arrow[1].y = hl + hs;
13400 arrow[2].y = hl;
13401 if (ts < 0.15)
13402 dc.SetPen(*pyelo_pen);
13403 else
13404 dc.SetPen(*pblue_pen);
13405 dc.DrawLines(3, arrow);
13406 }
13407 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13408 {
13409 hl = (int)(height * 0.5) + yDraw;
13410 arrow[0].y = hl;
13411 arrow[1].y = hl + hs;
13412 arrow[2].y = hl;
13413 if (ts < 0.40)
13414 dc.SetPen(*pyelo_pen);
13415 else
13416 dc.SetPen(*pblue_pen);
13417 dc.DrawLines(3, arrow);
13418 }
13419 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13420 {
13421 hl = (int)(height * 0.75) + yDraw;
13422 arrow[0].y = hl;
13423 arrow[1].y = hl + hs;
13424 arrow[2].y = hl;
13425 if (ts < 0.65)
13426 dc.SetPen(*pyelo_pen);
13427 else
13428 dc.SetPen(*pblue_pen);
13429 dc.DrawLines(3, arrow);
13430 }
13431 // draw tide level text
13432 wxString s;
13433 s.Printf("%3.1f", nowlev);
13434 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13435 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13436 int wx1;
13437 dc.GetTextExtent(s, &wx1, NULL);
13438 wx1 *= g_Platform->GetDisplayDIPMult(this);
13439 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13440 }
13441 }
13442 }
13443 }
13444 }
13445 }
13446 }
13447 }
13448}
13449
13450//------------------------------------------------------------------------------------------
13451// Currents Support
13452//------------------------------------------------------------------------------------------
13453
13454void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13455 if (!ptcmgr) return;
13456
13457 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13458
13459 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13460 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13461 double lon = pIDX->IDX_lon;
13462 double lat = pIDX->IDX_lat;
13463
13464 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13465 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13466 if ((BBox.Contains(lat, lon))) {
13467 // Manage the point selection list
13468 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13469 }
13470 }
13471 }
13472}
13473
13474void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13475 if (!ptcmgr) return;
13476
13477 float tcvalue, dir;
13478 bool bnew_val;
13479 char sbuf[20];
13480 wxFont *pTCFont;
13481 double lon_last = 0.;
13482 double lat_last = 0.;
13483 // arrow size for Raz Blanchard : 12 knots north
13484 double marge = 0.2;
13485 bool cur_time = !gTimeSource.IsValid();
13486
13487 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13488 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13489
13490 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13491 wxPENSTYLE_SOLID);
13492 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13493 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13494 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13495 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13496 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13497 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13498 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13499 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13500
13501 double skew_angle = GetVPRotation();
13502
13503 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13504 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13505 int font_size = wxMax(10, dFont->GetPointSize());
13506 font_size /= g_Platform->GetDisplayDIPMult(this);
13507 pTCFont = FontMgr::Get().FindOrCreateFont(
13508 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13509 false, dFont->GetFaceName());
13510
13511 float scale_factor = 1.0;
13512
13513 // Set the onscreen size of the symbol
13514 // Current report graphic is scaled by the text size of the label in use
13515 wxScreenDC sdc;
13516 int height;
13517 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13518 height *= g_Platform->GetDisplayDIPMult(this);
13519 float nominal_icon_size_pixels = 15;
13520 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13521
13522 scale_factor *= pix_factor;
13523
13524 float user_scale_factor = g_ChartScaleFactorExp;
13525 if (g_ChartScaleFactorExp > 1.0)
13526 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13527 1.2; // soften the scale factor a bit
13528
13529 scale_factor *= user_scale_factor;
13530
13531 scale_factor *= GetContentScaleFactor();
13532
13533 {
13534 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13535 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13536 double lon = pIDX->IDX_lon;
13537 double lat = pIDX->IDX_lat;
13538
13539 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13540 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13541 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13542 wxPoint r;
13543 GetCanvasPointPix(lat, lon, &r);
13544
13545 wxPoint d[4]; // points of a diamond at the current station location
13546 int dd = (int)(5.0 * scale_factor + 0.5);
13547 d[0].x = r.x;
13548 d[0].y = r.y + dd;
13549 d[1].x = r.x + dd;
13550 d[1].y = r.y;
13551 d[2].x = r.x;
13552 d[2].y = r.y - dd;
13553 d[3].x = r.x - dd;
13554 d[3].y = r.y;
13555
13556 if (1) {
13557 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13558 dc.SetPen(*pblack_pen);
13559 dc.SetBrush(*porange_brush);
13560 dc.DrawPolygon(4, d);
13561
13562 if (type == 'C') {
13563 dc.SetBrush(*pblack_brush);
13564 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13565 }
13566
13567 if (GetVP().chart_scale < 1000000) {
13568 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13569 continue;
13570 } else
13571 continue;
13572
13573 if (1 /*type == 'c'*/) {
13574 {
13575 // Get the display pixel location of the current station
13576 int pixxc, pixyc;
13577 pixxc = r.x;
13578 pixyc = r.y;
13579
13580 // Adjust drawing size using logarithmic scale. tcvalue is
13581 // current in knots
13582 double a1 = fabs(tcvalue) * 10.;
13583 // Current values <= 0.1 knot will have no arrow
13584 a1 = wxMax(1.0, a1);
13585 double a2 = log10(a1);
13586
13587 float cscale = scale_factor * a2 * 0.3;
13588
13589 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13590 dc.SetPen(*porange_pen);
13591 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13592 cscale);
13593 // Draw text, if enabled
13594
13595 if (bDrawCurrentValues) {
13596 dc.SetFont(*pTCFont);
13597 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13598 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13599 }
13600 }
13601 } // scale
13602 }
13603 /* This is useful for debugging the TC database
13604 else
13605 {
13606 dc.SetPen ( *porange_pen );
13607 dc.SetBrush ( *pgray_brush );
13608 dc.DrawPolygon ( 4, d );
13609 }
13610 */
13611 }
13612 lon_last = lon;
13613 lat_last = lat;
13614 }
13615 }
13616 }
13617}
13618
13619void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13620 ShowSingleTideDialog(x, y, pvIDX);
13621}
13622
13623void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13624 if (!pvIDX) return; // Validate input
13625
13626 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13627
13628 // Check if a tide dialog is already open and visible
13629 if (pCwin && pCwin->IsShown()) {
13630 // Same tide station: bring existing dialog to front (preserves user
13631 // context)
13632 if (pCwin->GetCurrentIDX() == pNewIDX) {
13633 pCwin->Raise();
13634 pCwin->SetFocus();
13635
13636 // Provide subtle visual feedback that dialog is already open
13637 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13638 return;
13639 }
13640
13641 // Different tide station: close current dialog before opening new one
13642 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13643 }
13644
13645 if (pCwin) {
13646 // This shouldn't happen but ensures clean state
13647 pCwin->Destroy();
13648 pCwin = NULL;
13649 }
13650
13651 // Create and display new tide dialog
13652 pCwin = new TCWin(this, x, y, pvIDX);
13653
13654 // Ensure the dialog is properly shown and focused
13655 if (pCwin) {
13656 pCwin->Show();
13657 pCwin->Raise();
13658 pCwin->SetFocus();
13659 }
13660}
13661
13662bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13663
13665 if (pCwin) {
13666 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13667 }
13668}
13669
13670#define NUM_CURRENT_ARROW_POINTS 9
13671static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13672 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13673 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13674 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13675
13676void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13677 double scale) {
13678 if (scale > 1e-2) {
13679 float sin_rot = sin(rot_angle * PI / 180.);
13680 float cos_rot = cos(rot_angle * PI / 180.);
13681
13682 // Move to the first point
13683
13684 float xt = CurrentArrowArray[0].x;
13685 float yt = CurrentArrowArray[0].y;
13686
13687 float xp = (xt * cos_rot) - (yt * sin_rot);
13688 float yp = (xt * sin_rot) + (yt * cos_rot);
13689 int x1 = (int)(xp * scale);
13690 int y1 = (int)(yp * scale);
13691
13692 // Walk thru the point list
13693 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13694 xt = CurrentArrowArray[ip].x;
13695 yt = CurrentArrowArray[ip].y;
13696
13697 float xp = (xt * cos_rot) - (yt * sin_rot);
13698 float yp = (xt * sin_rot) + (yt * cos_rot);
13699 int x2 = (int)(xp * scale);
13700 int y2 = (int)(yp * scale);
13701
13702 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13703
13704 x1 = x2;
13705 y1 = y2;
13706 }
13707 }
13708}
13709
13710wxString ChartCanvas::FindValidUploadPort() {
13711 wxString port;
13712 // Try to use the saved persistent upload port first
13713 if (!g_uploadConnection.IsEmpty() &&
13714 g_uploadConnection.StartsWith("Serial")) {
13715 port = g_uploadConnection;
13716 }
13717
13718 else {
13719 // If there is no persistent upload port recorded (yet)
13720 // then use the first available serial connection which has output defined.
13721 for (auto *cp : TheConnectionParams()) {
13722 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13723 port << "Serial:" << cp->Port;
13724 }
13725 }
13726 return port;
13727}
13728
13729void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13730 if (!win) return;
13731
13732 if (NULL == g_pais_query_dialog_active) {
13733 int pos_x = g_ais_query_dialog_x;
13734 int pos_y = g_ais_query_dialog_y;
13735
13736 if (g_pais_query_dialog_active) {
13737 g_pais_query_dialog_active->Destroy();
13738 g_pais_query_dialog_active = new AISTargetQueryDialog();
13739 } else {
13740 g_pais_query_dialog_active = new AISTargetQueryDialog();
13741 }
13742
13743 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13744 wxPoint(pos_x, pos_y));
13745
13746 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13747 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13748 g_pais_query_dialog_active->SetMMSI(mmsi);
13749 g_pais_query_dialog_active->UpdateText();
13750 wxSize sz = g_pais_query_dialog_active->GetSize();
13751
13752 bool b_reset_pos = false;
13753#ifdef __WXMSW__
13754 // Support MultiMonitor setups which an allow negative window positions.
13755 // If the requested window title bar does not intersect any installed
13756 // monitor, then default to simple primary monitor positioning.
13757 RECT frame_title_rect;
13758 frame_title_rect.left = pos_x;
13759 frame_title_rect.top = pos_y;
13760 frame_title_rect.right = pos_x + sz.x;
13761 frame_title_rect.bottom = pos_y + 30;
13762
13763 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13764 b_reset_pos = true;
13765#else
13766
13767 // Make sure drag bar (title bar) of window intersects wxClient Area of
13768 // screen, with a little slop...
13769 wxRect window_title_rect; // conservative estimate
13770 window_title_rect.x = pos_x;
13771 window_title_rect.y = pos_y;
13772 window_title_rect.width = sz.x;
13773 window_title_rect.height = 30;
13774
13775 wxRect ClientRect = wxGetClientDisplayRect();
13776 ClientRect.Deflate(
13777 60, 60); // Prevent the new window from being too close to the edge
13778 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13779
13780#endif
13781
13782 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13783
13784 } else {
13785 g_pais_query_dialog_active->SetMMSI(mmsi);
13786 g_pais_query_dialog_active->UpdateText();
13787 }
13788
13789 g_pais_query_dialog_active->Show();
13790}
13791
13792void ChartCanvas::ToggleCanvasQuiltMode() {
13793 bool cur_mode = GetQuiltMode();
13794
13795 if (!GetQuiltMode())
13796 SetQuiltMode(true);
13797 else if (GetQuiltMode()) {
13798 SetQuiltMode(false);
13799 g_sticky_chart = GetQuiltReferenceChartIndex();
13800 }
13801
13802 if (cur_mode != GetQuiltMode()) {
13803 SetupCanvasQuiltMode();
13804 DoCanvasUpdate();
13805 InvalidateGL();
13806 Refresh();
13807 }
13808 // TODO What to do about this?
13809 // g_bQuiltEnable = GetQuiltMode();
13810
13811 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13812 if (ps52plib) ps52plib->GenerateStateHash();
13813
13814 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13815 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13816}
13817
13818void ChartCanvas::DoCanvasStackDelta(int direction) {
13819 if (!GetQuiltMode()) {
13820 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13821 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13822 if ((current_stack_index + direction) < 0) return;
13823
13824 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13825 int new_dbIndex =
13826 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13827
13828 if (IsChartQuiltableRef(new_dbIndex)) {
13829 ToggleCanvasQuiltMode();
13830 SelectQuiltRefdbChart(new_dbIndex);
13831 m_bpersistent_quilt = false;
13832 }
13833 } else {
13834 SelectChartFromStack(current_stack_index + direction);
13835 }
13836 } else {
13837 std::vector<int> piano_chart_index_array =
13838 GetQuiltExtendedStackdbIndexArray();
13839 int refdb = GetQuiltRefChartdbIndex();
13840
13841 // Find the ref chart in the stack
13842 int current_index = -1;
13843 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13844 if (refdb == piano_chart_index_array[i]) {
13845 current_index = i;
13846 break;
13847 }
13848 }
13849 if (current_index == -1) return;
13850
13851 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13852 int target_family = ctet.GetChartFamily();
13853
13854 int new_index = -1;
13855 int check_index = current_index + direction;
13856 bool found = false;
13857 int check_dbIndex = -1;
13858 int new_dbIndex = -1;
13859
13860 // When quilted. switch within the same chart family
13861 while (!found &&
13862 (unsigned int)check_index < piano_chart_index_array.size() &&
13863 (check_index >= 0)) {
13864 check_dbIndex = piano_chart_index_array[check_index];
13865 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13866 if (target_family == cte.GetChartFamily()) {
13867 found = true;
13868 new_index = check_index;
13869 new_dbIndex = check_dbIndex;
13870 break;
13871 }
13872
13873 check_index += direction;
13874 }
13875
13876 if (!found) return;
13877
13878 if (!IsChartQuiltableRef(new_dbIndex)) {
13879 ToggleCanvasQuiltMode();
13880 SelectdbChart(new_dbIndex);
13881 m_bpersistent_quilt = true;
13882 } else {
13883 SelectQuiltRefChart(new_index);
13884 }
13885 }
13886
13887 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13888 // (checkmarks etc)
13889 SetQuiltChartHiLiteIndex(-1);
13890
13891 ReloadVP();
13892}
13893
13894//--------------------------------------------------------------------------------------------------------
13895//
13896// Toolbar support
13897//
13898//--------------------------------------------------------------------------------------------------------
13899
13900void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13901 // Handle the per-canvas toolbar clicks here
13902
13903 switch (event.GetId()) {
13904 case ID_ZOOMIN: {
13905 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13906 break;
13907 }
13908
13909 case ID_ZOOMOUT: {
13910 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13911 break;
13912 }
13913
13914 case ID_STKUP:
13915 DoCanvasStackDelta(1);
13916 DoCanvasUpdate();
13917 break;
13918
13919 case ID_STKDN:
13920 DoCanvasStackDelta(-1);
13921 DoCanvasUpdate();
13922 break;
13923
13924 case ID_FOLLOW: {
13925 TogglebFollow();
13926 break;
13927 }
13928
13929 case ID_CURRENT: {
13930 ShowCurrents(!GetbShowCurrent());
13931 ReloadVP();
13932 Refresh(false);
13933 break;
13934 }
13935
13936 case ID_TIDE: {
13937 ShowTides(!GetbShowTide());
13938 ReloadVP();
13939 Refresh(false);
13940 break;
13941 }
13942
13943 case ID_ROUTE: {
13944 if (0 == m_routeState) {
13945 StartRoute();
13946 } else {
13947 FinishRoute();
13948 }
13949
13950#ifdef __ANDROID__
13951 androidSetRouteAnnunciator(m_routeState == 1);
13952#endif
13953 break;
13954 }
13955
13956 case ID_AIS: {
13957 SetAISCanvasDisplayStyle(-1);
13958 break;
13959 }
13960
13961 default:
13962 break;
13963 }
13964
13965 // And then let gFrame handle the rest....
13966 event.Skip();
13967}
13968
13969void ChartCanvas::SetShowAIS(bool show) {
13970 m_bShowAIS = show;
13971 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13972 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13973}
13974
13975void ChartCanvas::SetAttenAIS(bool show) {
13976 m_bShowAISScaled = show;
13977 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13978 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13979}
13980
13981void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
13982 // make some arrays to hold the dfferences between cycle steps
13983 // show all, scaled, hide all
13984 bool bShowAIS_Array[3] = {true, true, false};
13985 bool bShowScaled_Array[3] = {false, true, true};
13986 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
13987 _("Attenuate less critical AIS targets"),
13988 _("Hide AIS Targets")};
13989 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
13990 int ArraySize = 3;
13991 int AIS_Toolbar_Switch = 0;
13992 if (StyleIndx == -1) { // -1 means coming from toolbar button
13993 // find current state of switch
13994 for (int i = 1; i < ArraySize; i++) {
13995 if ((bShowAIS_Array[i] == m_bShowAIS) &&
13996 (bShowScaled_Array[i] == m_bShowAISScaled))
13997 AIS_Toolbar_Switch = i;
13998 }
13999 AIS_Toolbar_Switch++; // we did click so continu with next item
14000 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14001 AIS_Toolbar_Switch++;
14002
14003 } else { // coming from menu bar.
14004 AIS_Toolbar_Switch = StyleIndx;
14005 }
14006 // make sure we are not above array
14007 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14008
14009 int AIS_Toolbar_Switch_Next =
14010 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14011 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14012 AIS_Toolbar_Switch_Next++;
14013 if (AIS_Toolbar_Switch_Next >= ArraySize)
14014 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14015
14016 // Set found values to global and member variables
14017 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14018 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14019}
14020
14021void ChartCanvas::TouchAISToolActive() {}
14022
14023void ChartCanvas::UpdateAISTBTool() {}
14024
14025//---------------------------------------------------------------------------------
14026//
14027// Compass/GPS status icon support
14028//
14029//---------------------------------------------------------------------------------
14030
14031void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14032 // Look for change in overlap or positions
14033 bool b_update = false;
14034 int cc1_edge_comp = 2;
14035 wxRect rect = m_Compass->GetRect();
14036 wxSize parent_size = GetSize();
14037
14038 parent_size *= m_displayScale;
14039
14040 // check to see if it would overlap if it was in its home position (upper
14041 // right)
14042 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14043 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14044 wxRect compass_rect(compass_pt, rect.GetSize());
14045
14046 m_Compass->Move(compass_pt);
14047
14048 if (m_Compass && m_Compass->IsShown())
14049 m_Compass->UpdateStatus(b_force_new | b_update);
14050
14051 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14052 scaler = wxMax(scaler, 1.0);
14053 wxPoint note_point = wxPoint(
14054 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14055 if (m_notification_button) {
14056 m_notification_button->Move(note_point);
14057 m_notification_button->UpdateStatus();
14058 }
14059
14060 if (b_force_new | b_update) Refresh();
14061}
14062
14063void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14064 ChartTypeEnum New_Type,
14065 ChartFamilyEnum New_Family) {
14066 if (!GetpCurrentStack()) return;
14067 if (!ChartData) return;
14068
14069 if (index < GetpCurrentStack()->nEntry) {
14070 // Open the new chart
14071 ChartBase *pTentative_Chart;
14072 pTentative_Chart = ChartData->OpenStackChartConditional(
14073 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14074
14075 if (pTentative_Chart) {
14076 if (m_singleChart) m_singleChart->Deactivate();
14077
14078 m_singleChart = pTentative_Chart;
14079 m_singleChart->Activate();
14080
14081 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14082 GetpCurrentStack(), m_singleChart->GetFullPath());
14083 }
14084
14085 // Setup the view
14086 double zLat, zLon;
14087 if (m_bFollow) {
14088 zLat = gLat;
14089 zLon = gLon;
14090 } else {
14091 zLat = m_vLat;
14092 zLon = m_vLon;
14093 }
14094
14095 double best_scale_ppm = GetBestVPScale(m_singleChart);
14096 double rotation = GetVPRotation();
14097 double oldskew = GetVPSkew();
14098 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14099
14100 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14101 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14102 if (fabs(newskew) > 0.0001) rotation = newskew;
14103 }
14104
14105 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14106
14107 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14108 }
14109
14110 // refresh Piano
14111 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14112 if (idx < 0) return;
14113
14114 std::vector<int> piano_active_chart_index_array;
14115 piano_active_chart_index_array.push_back(
14116 GetpCurrentStack()->GetCurrentEntrydbIndex());
14117 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14118}
14119
14120void ChartCanvas::SelectdbChart(int dbindex) {
14121 if (!GetpCurrentStack()) return;
14122 if (!ChartData) return;
14123
14124 if (dbindex >= 0) {
14125 // Open the new chart
14126 ChartBase *pTentative_Chart;
14127 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14128
14129 if (pTentative_Chart) {
14130 if (m_singleChart) m_singleChart->Deactivate();
14131
14132 m_singleChart = pTentative_Chart;
14133 m_singleChart->Activate();
14134
14135 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14136 GetpCurrentStack(), m_singleChart->GetFullPath());
14137 }
14138
14139 // Setup the view
14140 double zLat, zLon;
14141 if (m_bFollow) {
14142 zLat = gLat;
14143 zLon = gLon;
14144 } else {
14145 zLat = m_vLat;
14146 zLon = m_vLon;
14147 }
14148
14149 double best_scale_ppm = GetBestVPScale(m_singleChart);
14150
14151 if (m_singleChart)
14152 SetViewPoint(zLat, zLon, best_scale_ppm,
14153 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14154
14155 // SetChartUpdatePeriod( );
14156
14157 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14158 }
14159
14160 // TODO refresh_Piano();
14161}
14162
14163void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14164 double target_scale = GetVP().view_scale_ppm;
14165
14166 if (!GetQuiltMode()) {
14167 if (GetpCurrentStack()) {
14168 int stack_index = -1;
14169 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14170 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14171 if (check_dbIndex < 0) continue;
14172 const ChartTableEntry &cte =
14173 ChartData->GetChartTableEntry(check_dbIndex);
14174 if (type == cte.GetChartType()) {
14175 stack_index = i;
14176 break;
14177 } else if (family == cte.GetChartFamily()) {
14178 stack_index = i;
14179 break;
14180 }
14181 }
14182
14183 if (stack_index >= 0) {
14184 SelectChartFromStack(stack_index);
14185 }
14186 }
14187 } else {
14188 int sel_dbIndex = -1;
14189 std::vector<int> piano_chart_index_array =
14190 GetQuiltExtendedStackdbIndexArray();
14191 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14192 int check_dbIndex = piano_chart_index_array[i];
14193 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14194 if (type == cte.GetChartType()) {
14195 if (IsChartQuiltableRef(check_dbIndex)) {
14196 sel_dbIndex = check_dbIndex;
14197 break;
14198 }
14199 } else if (family == cte.GetChartFamily()) {
14200 if (IsChartQuiltableRef(check_dbIndex)) {
14201 sel_dbIndex = check_dbIndex;
14202 break;
14203 }
14204 }
14205 }
14206
14207 if (sel_dbIndex >= 0) {
14208 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14209 // Re-qualify the quilt reference chart selection
14210 AdjustQuiltRefChart();
14211 }
14212
14213 // Now reset the scale to the target...
14214 SetVPScale(target_scale);
14215 }
14216
14217 SetQuiltChartHiLiteIndex(-1);
14218
14219 ReloadVP();
14220}
14221
14222bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14223 return std::find(m_tile_yesshow_index_array.begin(),
14224 m_tile_yesshow_index_array.end(),
14225 index) != m_tile_yesshow_index_array.end();
14226}
14227
14228bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14229 return std::find(m_tile_noshow_index_array.begin(),
14230 m_tile_noshow_index_array.end(),
14231 index) != m_tile_noshow_index_array.end();
14232}
14233
14234void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14235 if (std::find(m_tile_noshow_index_array.begin(),
14236 m_tile_noshow_index_array.end(),
14237 index) == m_tile_noshow_index_array.end()) {
14238 m_tile_noshow_index_array.push_back(index);
14239 }
14240}
14241
14242//-------------------------------------------------------------------------------------------------------
14243//
14244// Piano support
14245//
14246//-------------------------------------------------------------------------------------------------------
14247
14248void ChartCanvas::HandlePianoClick(
14249 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14250 if (g_options && g_options->IsShown())
14251 return; // Piano might be invalid due to chartset updates.
14252 if (!m_pCurrentStack) return;
14253 if (!ChartData) return;
14254
14255 // stop movement or on slow computer we may get something like :
14256 // zoom out with the wheel (timer is set)
14257 // quickly click and display a chart, which may zoom in
14258 // but the delayed timer fires first and it zooms out again!
14259 StopMovement();
14260
14261 // When switching by piano key click, we may appoint the new target chart to
14262 // be any chart in the composite array.
14263 // As an improvement to UX, find the chart that is "closest" to the current
14264 // vp,
14265 // and select that chart. This will cause a jump to the centroid of that
14266 // chart
14267
14268 double distance = 25000; // RTW
14269 int closest_index = -1;
14270 for (int chart_index : selected_dbIndex_array) {
14271 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14272 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14273 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14274
14275 // measure distance as Manhattan style
14276 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14277 if (test_distance < distance) {
14278 distance = test_distance;
14279 closest_index = chart_index;
14280 }
14281 }
14282
14283 int selected_dbIndex = selected_dbIndex_array[0];
14284 if (closest_index >= 0) selected_dbIndex = closest_index;
14285
14286 if (!GetQuiltMode()) {
14287 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14288 if (IsChartQuiltableRef(selected_dbIndex)) {
14289 ToggleCanvasQuiltMode();
14290 SelectQuiltRefdbChart(selected_dbIndex);
14291 m_bpersistent_quilt = false;
14292 } else {
14293 SelectChartFromStack(selected_index);
14294 }
14295 } else {
14296 SelectChartFromStack(selected_index);
14297 g_sticky_chart = selected_dbIndex;
14298 }
14299
14300 if (m_singleChart)
14301 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14302 } else {
14303 // Handle MBTiles overlays first
14304 // Left click simply toggles the noshow array index entry
14305 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14306 bool bfound = false;
14307 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14308 if (m_tile_noshow_index_array[i] ==
14309 selected_dbIndex) { // chart is in the noshow list
14310 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14311 i); // erase it
14312 bfound = true;
14313 break;
14314 }
14315 }
14316 if (!bfound) {
14317 m_tile_noshow_index_array.push_back(selected_dbIndex);
14318 }
14319
14320 // If not already present, add this tileset to the "yes_show" array.
14321 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14322 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14323 }
14324
14325 else {
14326 if (IsChartQuiltableRef(selected_dbIndex)) {
14327 // if( ChartData ) ChartData->PurgeCache();
14328
14329 // If the chart is a vector chart, and of very large scale,
14330 // then we had better set the new scale directly to avoid excessive
14331 // underzoom on, eg, Inland ENCs
14332 bool set_scale = false;
14333 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14334 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14335 set_scale = true;
14336 }
14337 }
14338
14339 if (!set_scale) {
14340 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14341 } else {
14342 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14343
14344 // Adjust scale so that the selected chart is underzoomed/overzoomed
14345 // by a controlled amount
14346 ChartBase *pc =
14347 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14348 if (pc) {
14349 double proposed_scale_onscreen =
14351
14352 if (g_bPreserveScaleOnX) {
14353 proposed_scale_onscreen =
14354 wxMin(proposed_scale_onscreen,
14355 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14356 GetCanvasWidth()));
14357 } else {
14358 proposed_scale_onscreen =
14359 wxMin(proposed_scale_onscreen,
14360 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14361 GetCanvasWidth()));
14362
14363 proposed_scale_onscreen =
14364 wxMax(proposed_scale_onscreen,
14365 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14367 }
14368
14369 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14370 }
14371 }
14372 } else {
14373 ToggleCanvasQuiltMode();
14374 SelectdbChart(selected_dbIndex);
14375 m_bpersistent_quilt = true;
14376 }
14377 }
14378 }
14379
14380 SetQuiltChartHiLiteIndex(-1);
14381 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14382 // (checkmarks etc)
14383 HideChartInfoWindow();
14384 DoCanvasUpdate();
14385 ReloadVP(); // Pick up the new selections
14386}
14387
14388void ChartCanvas::HandlePianoRClick(
14389 int x, int y, int selected_index,
14390 const std::vector<int> &selected_dbIndex_array) {
14391 if (g_options && g_options->IsShown())
14392 return; // Piano might be invalid due to chartset updates.
14393 if (!GetpCurrentStack()) return;
14394
14395 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14396 UpdateCanvasControlBar();
14397
14398 SetQuiltChartHiLiteIndex(-1);
14399}
14400
14401void ChartCanvas::HandlePianoRollover(
14402 int selected_index, const std::vector<int> &selected_dbIndex_array,
14403 int n_charts, int scale) {
14404 if (g_options && g_options->IsShown())
14405 return; // Piano might be invalid due to chartset updates.
14406 if (!GetpCurrentStack()) return;
14407 if (!ChartData) return;
14408
14409 if (ChartData->IsBusy()) return;
14410
14411 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14412
14413 if (!GetQuiltMode()) {
14414 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14415 } else {
14416 // Select the correct vector
14417 std::vector<int> piano_chart_index_array;
14418 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14419 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14420 if ((GetpCurrentStack()->nEntry > 1) ||
14421 (piano_chart_index_array.size() >= 1)) {
14422 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14423
14424 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14425 ReloadVP(false); // no VP adjustment allowed
14426 } else if (GetpCurrentStack()->nEntry == 1) {
14427 const ChartTableEntry &cte =
14428 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14429 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14430 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14431 ReloadVP(false);
14432 } else if ((-1 == selected_index) &&
14433 (0 == selected_dbIndex_array.size())) {
14434 ShowChartInfoWindow(key_location.x, -1);
14435 }
14436 }
14437 } else {
14438 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14439
14440 if ((GetpCurrentStack()->nEntry > 1) ||
14441 (piano_chart_index_array.size() >= 1)) {
14442 if (n_charts > 1)
14443 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14444 selected_dbIndex_array);
14445 else if (n_charts == 1)
14446 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14447
14448 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14449 ReloadVP(false); // no VP adjustment allowed
14450 }
14451 }
14452 }
14453}
14454
14455void ChartCanvas::ClearPianoRollover() {
14456 ClearQuiltChartHiLiteIndexArray();
14457 ShowChartInfoWindow(0, -1);
14458 std::vector<int> vec;
14459 ShowCompositeInfoWindow(0, 0, 0, vec);
14460 ReloadVP(false);
14461}
14462
14463void ChartCanvas::UpdateCanvasControlBar() {
14464 if (m_pianoFrozen) return;
14465
14466 if (!GetpCurrentStack()) return;
14467 if (!ChartData) return;
14468 if (!g_bShowChartBar) return;
14469
14470 int sel_type = -1;
14471 int sel_family = -1;
14472
14473 std::vector<int> piano_chart_index_array;
14474 std::vector<int> empty_piano_chart_index_array;
14475
14476 wxString old_hash = m_Piano->GetStoredHash();
14477
14478 if (GetQuiltMode()) {
14479 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14480 GetQuiltFullScreendbIndexArray());
14481
14482 std::vector<int> piano_active_chart_index_array =
14483 GetQuiltCandidatedbIndexArray();
14484 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14485
14486 std::vector<int> piano_eclipsed_chart_index_array =
14487 GetQuiltEclipsedStackdbIndexArray();
14488 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14489
14490 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14491 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14492
14493 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14494 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14495 } else {
14496 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14497 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14498 // TODO refresh_Piano();
14499
14500 if (m_singleChart) {
14501 sel_type = m_singleChart->GetChartType();
14502 sel_family = m_singleChart->GetChartFamily();
14503 }
14504 }
14505
14506 // Set up the TMerc and Skew arrays
14507 std::vector<int> piano_skew_chart_index_array;
14508 std::vector<int> piano_tmerc_chart_index_array;
14509 std::vector<int> piano_poly_chart_index_array;
14510
14511 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14512 const ChartTableEntry &ctei =
14513 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14514 double skew_norm = ctei.GetChartSkew();
14515 if (skew_norm > 180.) skew_norm -= 360.;
14516
14517 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14518 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14519
14520 // Polyconic skewed charts should show as skewed
14521 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14522 if (fabs(skew_norm) > 1.)
14523 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14524 else
14525 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14526 } else if (fabs(skew_norm) > 1.)
14527 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14528 }
14529 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14530 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14531 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14532
14533 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14534 if (new_hash != old_hash) {
14535 m_Piano->FormatKeys();
14536 HideChartInfoWindow();
14537 m_Piano->ResetRollover();
14538 SetQuiltChartHiLiteIndex(-1);
14539 m_brepaint_piano = true;
14540 }
14541
14542 // Create a bitmask int that describes what Family/Type of charts are shown in
14543 // the bar, and notify the platform.
14544 int mask = 0;
14545 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14546 const ChartTableEntry &ctei =
14547 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14548 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14549 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14550 if (e == CHART_FAMILY_RASTER) mask |= 1;
14551 if (e == CHART_FAMILY_VECTOR) {
14552 if (t == CHART_TYPE_CM93COMP)
14553 mask |= 4;
14554 else
14555 mask |= 2;
14556 }
14557 }
14558
14559 wxString s_indicated;
14560 if (sel_type == CHART_TYPE_CM93COMP)
14561 s_indicated = "cm93";
14562 else {
14563 if (sel_family == CHART_FAMILY_RASTER)
14564 s_indicated = "raster";
14565 else if (sel_family == CHART_FAMILY_VECTOR)
14566 s_indicated = "vector";
14567 }
14568
14569 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14570}
14571
14572void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14573
14574void ChartCanvas::PianoPopupMenu(
14575 int x, int y, int selected_index,
14576 const std::vector<int> &selected_dbIndex_array) {
14577 if (!GetpCurrentStack()) return;
14578
14579 // No context menu if quilting is disabled
14580 if (!GetQuiltMode()) return;
14581
14582 m_piano_ctx_menu = new wxMenu();
14583
14584 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14585 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14586 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14587 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14588 } else {
14589 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14590 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14591 // wxEVT_COMMAND_MENU_SELECTED,
14592 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14593
14594 menu_selected_dbIndex = selected_dbIndex_array[0];
14595 menu_selected_index = selected_index;
14596
14597 // Search the no-show array
14598 bool b_is_in_noshow = false;
14599 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14600 if (m_quilt_noshow_index_array[i] ==
14601 menu_selected_dbIndex) // chart is in the noshow list
14602 {
14603 b_is_in_noshow = true;
14604 break;
14605 }
14606 }
14607
14608 if (b_is_in_noshow) {
14609 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14610 _("Show This Chart"));
14611 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14612 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14613 } else if (GetpCurrentStack()->nEntry > 1) {
14614 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14615 _("Hide This Chart"));
14616 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14617 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14618 }
14619 }
14620
14621 wxPoint pos = wxPoint(x, y - 30);
14622
14623 // Invoke the drop-down menu
14624 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14625 PopupMenu(m_piano_ctx_menu, pos);
14626
14627 delete m_piano_ctx_menu;
14628 m_piano_ctx_menu = NULL;
14629
14630 HideChartInfoWindow();
14631 m_Piano->ResetRollover();
14632
14633 SetQuiltChartHiLiteIndex(-1);
14634 ClearQuiltChartHiLiteIndexArray();
14635
14636 ReloadVP();
14637}
14638
14639void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14640 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14641 if (m_quilt_noshow_index_array[i] ==
14642 menu_selected_dbIndex) // chart is in the noshow list
14643 {
14644 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14645 break;
14646 }
14647 }
14648}
14649
14650void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14651 if (!GetpCurrentStack()) return;
14652 if (!ChartData) return;
14653
14654 RemoveChartFromQuilt(menu_selected_dbIndex);
14655
14656 // It could happen that the chart being disabled is the reference
14657 // chart....
14658 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14659 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14660
14661 int i = menu_selected_index + 1; // select next smaller scale chart
14662 bool b_success = false;
14663 while (i < GetpCurrentStack()->nEntry - 1) {
14664 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14665 if (type == ChartData->GetDBChartType(dbIndex)) {
14666 SelectQuiltRefChart(i);
14667 b_success = true;
14668 break;
14669 }
14670 i++;
14671 }
14672
14673 // If that did not work, try to select the next larger scale compatible
14674 // chart
14675 if (!b_success) {
14676 i = menu_selected_index - 1;
14677 while (i > 0) {
14678 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14679 if (type == ChartData->GetDBChartType(dbIndex)) {
14680 SelectQuiltRefChart(i);
14681 b_success = true;
14682 break;
14683 }
14684 i--;
14685 }
14686 }
14687 }
14688}
14689
14690void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14691 // Remove the item from the list (if it appears) to avoid multiple addition
14692 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14693 if (m_quilt_noshow_index_array[i] ==
14694 dbIndex) // chart is already in the noshow list
14695 {
14696 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14697 break;
14698 }
14699 }
14700
14701 m_quilt_noshow_index_array.push_back(dbIndex);
14702}
14703
14704bool ChartCanvas::UpdateS52State() {
14705 bool retval = false;
14706
14707 if (ps52plib) {
14708 ps52plib->SetShowS57Text(m_encShowText);
14709 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14710 ps52plib->m_bShowSoundg = m_encShowDepth;
14711 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14712 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14713
14714 // Lights
14715 if (!m_encShowLights) // On, going off
14716 ps52plib->AddObjNoshow("LIGHTS");
14717 else // Off, going on
14718 ps52plib->RemoveObjNoshow("LIGHTS");
14719 ps52plib->SetLightsOff(!m_encShowLights);
14720 ps52plib->m_bExtendLightSectors = true;
14721
14722 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14723 ps52plib->SetAnchorOn(m_encShowAnchor);
14724 ps52plib->SetQualityOfData(m_encShowDataQual);
14725 }
14726
14727 return retval;
14728}
14729
14730void ChartCanvas::SetShowENCDataQual(bool show) {
14731 m_encShowDataQual = show;
14732 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14733 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14734
14735 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14736}
14737
14738void ChartCanvas::SetShowENCText(bool show) {
14739 m_encShowText = show;
14740 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14741 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14742
14743 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14744}
14745
14746void ChartCanvas::SetENCDisplayCategory(int category) {
14747 m_encDisplayCategory = category;
14748 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14749}
14750
14751void ChartCanvas::SetShowENCDepth(bool show) {
14752 m_encShowDepth = show;
14753 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14754 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14755
14756 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14757}
14758
14759void ChartCanvas::SetShowENCLightDesc(bool show) {
14760 m_encShowLightDesc = show;
14761 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14762 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14763
14764 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14765}
14766
14767void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14768 m_encShowBuoyLabels = show;
14769 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14770}
14771
14772void ChartCanvas::SetShowENCLights(bool show) {
14773 m_encShowLights = show;
14774 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14775 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14776
14777 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14778}
14779
14780void ChartCanvas::SetShowENCAnchor(bool show) {
14781 m_encShowAnchor = show;
14782 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14783 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14784
14785 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14786}
14787
14788wxRect ChartCanvas::GetMUIBarRect() {
14789 wxRect rv;
14790 if (m_muiBar) {
14791 rv = m_muiBar->GetRect();
14792 }
14793
14794 return rv;
14795}
14796
14797void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14798 if (!GetAlertString().IsEmpty()) {
14799 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14800 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14801
14802 dc.SetFont(*pfont);
14803 dc.SetPen(*wxTRANSPARENT_PEN);
14804
14805 dc.SetBrush(wxColour(243, 229, 47));
14806 int w, h;
14807 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14808 h += 2;
14809 // int yp = vp.pix_height - 20 - h;
14810
14811 wxRect sbr = GetScaleBarRect();
14812 int xp = sbr.x + sbr.width + 10;
14813 int yp = (sbr.y + sbr.height) - h;
14814
14815 int wdraw = w + 10;
14816 dc.DrawRectangle(xp, yp, wdraw, h);
14817 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14818 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14819 }
14820}
14821
14822//--------------------------------------------------------------------------------------------------------
14823// Screen Brightness Control Support Routines
14824//
14825//--------------------------------------------------------------------------------------------------------
14826
14827#ifdef __UNIX__
14828#define BRIGHT_XCALIB
14829#define __OPCPN_USEICC__
14830#endif
14831
14832#ifdef __OPCPN_USEICC__
14833int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14834 double co_green, double co_blue);
14835
14836wxString temp_file_name;
14837#endif
14838
14839#if 0
14840class ocpnCurtain: public wxDialog
14841{
14842 DECLARE_CLASS( ocpnCurtain )
14843 DECLARE_EVENT_TABLE()
14844
14845public:
14846 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14847 ~ocpnCurtain( );
14848 bool ProcessEvent(wxEvent& event);
14849
14850};
14851
14852IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14853
14854BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14855END_EVENT_TABLE()
14856
14857ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14858{
14859 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14860}
14861
14862ocpnCurtain::~ocpnCurtain()
14863{
14864}
14865
14866bool ocpnCurtain::ProcessEvent(wxEvent& event)
14867{
14868 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14869 return GetParent()->GetEventHandler()->ProcessEvent(event);
14870}
14871#endif
14872
14873#ifdef _WIN32
14874#include <windows.h>
14875
14876HMODULE hGDI32DLL;
14877typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14878typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14879SetDeviceGammaRamp_ptr_type
14880 g_pSetDeviceGammaRamp; // the API entry points in the dll
14881GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14882
14883WORD *g_pSavedGammaMap;
14884
14885#endif
14886
14887int InitScreenBrightness() {
14888#ifdef _WIN32
14889#ifdef ocpnUSE_GL
14890 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14891 HDC hDC;
14892 BOOL bbr;
14893
14894 if (NULL == hGDI32DLL) {
14895 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14896
14897 if (NULL != hGDI32DLL) {
14898 // Get the entry points of the required functions
14899 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14900 hGDI32DLL, "SetDeviceGammaRamp");
14901 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14902 hGDI32DLL, "GetDeviceGammaRamp");
14903
14904 // If the functions are not found, unload the DLL and return false
14905 if ((NULL == g_pSetDeviceGammaRamp) ||
14906 (NULL == g_pGetDeviceGammaRamp)) {
14907 FreeLibrary(hGDI32DLL);
14908 hGDI32DLL = NULL;
14909 return 0;
14910 }
14911 }
14912 }
14913
14914 // Interface is ready, so....
14915 // Get some storage
14916 if (!g_pSavedGammaMap) {
14917 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14918
14919 hDC = GetDC(NULL); // Get the full screen DC
14920 bbr = g_pGetDeviceGammaRamp(
14921 hDC, g_pSavedGammaMap); // Get the existing ramp table
14922 ReleaseDC(NULL, hDC); // Release the DC
14923 }
14924
14925 // On Windows hosts, try to adjust the registry to allow full range
14926 // setting of Gamma table This is an undocumented Windows hack.....
14927 wxRegKey *pRegKey = new wxRegKey(
14928 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
14929 "NT\\CurrentVersion\\ICM");
14930 if (!pRegKey->Exists()) pRegKey->Create();
14931 pRegKey->SetValue("GdiIcmGammaRange", 256);
14932
14933 g_brightness_init = true;
14934 return 1;
14935 }
14936#endif
14937
14938 {
14939 if (NULL == g_pcurtain) {
14940 if (gFrame->CanSetTransparent()) {
14941 // Build the curtain window
14942 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, "",
14943 wxPoint(0, 0), ::wxGetDisplaySize(),
14944 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14945 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14946
14947 // g_pcurtain = new ocpnCurtain(gFrame,
14948 // wxPoint(0,0),::wxGetDisplaySize(),
14949 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14950 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14951
14952 g_pcurtain->Hide();
14953
14954 HWND hWnd = GetHwndOf(g_pcurtain);
14955 SetWindowLong(hWnd, GWL_EXSTYLE,
14956 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14957 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14958 g_pcurtain->SetTransparent(0);
14959
14960 g_pcurtain->Maximize();
14961 g_pcurtain->Show();
14962
14963 // All of this is obtuse, but necessary for Windows...
14964 g_pcurtain->Enable();
14965 g_pcurtain->Disable();
14966
14967 gFrame->Disable();
14968 gFrame->Enable();
14969 // SetFocus();
14970 }
14971 }
14972 g_brightness_init = true;
14973
14974 return 1;
14975 }
14976#else
14977 // Look for "xcalib" application
14978 wxString cmd("xcalib -version");
14979
14980 wxArrayString output;
14981 long r = wxExecute(cmd, output);
14982 if (0 != r)
14983 wxLogMessage(
14984 " External application \"xcalib\" not found. Screen brightness "
14985 "not changed.");
14986
14987 g_brightness_init = true;
14988 return 0;
14989#endif
14990}
14991
14992int RestoreScreenBrightness() {
14993#ifdef _WIN32
14994
14995 if (g_pSavedGammaMap) {
14996 HDC hDC = GetDC(NULL); // Get the full screen DC
14997 g_pSetDeviceGammaRamp(hDC,
14998 g_pSavedGammaMap); // Restore the saved ramp table
14999 ReleaseDC(NULL, hDC); // Release the DC
15000
15001 free(g_pSavedGammaMap);
15002 g_pSavedGammaMap = NULL;
15003 }
15004
15005 if (g_pcurtain) {
15006 g_pcurtain->Close();
15007 g_pcurtain->Destroy();
15008 g_pcurtain = NULL;
15009 }
15010
15011 g_brightness_init = false;
15012 return 1;
15013
15014#endif
15015
15016#ifdef BRIGHT_XCALIB
15017 if (g_brightness_init) {
15018 wxString cmd;
15019 cmd = "xcalib -clear";
15020 wxExecute(cmd, wxEXEC_ASYNC);
15021 g_brightness_init = false;
15022 }
15023
15024 return 1;
15025#endif
15026
15027 return 0;
15028}
15029
15030// Set brightness. [0..100]
15031int SetScreenBrightness(int brightness) {
15032#ifdef _WIN32
15033
15034 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15035 // some (most modern?) versions of gdi32.dll Load the required library dll,
15036 // if not already in place
15037#ifdef ocpnUSE_GL
15038 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
15039 if (g_pcurtain) {
15040 g_pcurtain->Close();
15041 g_pcurtain->Destroy();
15042 g_pcurtain = NULL;
15043 }
15044
15045 InitScreenBrightness();
15046
15047 if (NULL == hGDI32DLL) {
15048 // Unicode stuff.....
15049 wchar_t wdll_name[80];
15050 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15051 LPCWSTR cstr = wdll_name;
15052
15053 hGDI32DLL = LoadLibrary(cstr);
15054
15055 if (NULL != hGDI32DLL) {
15056 // Get the entry points of the required functions
15057 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15058 hGDI32DLL, "SetDeviceGammaRamp");
15059 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15060 hGDI32DLL, "GetDeviceGammaRamp");
15061
15062 // If the functions are not found, unload the DLL and return false
15063 if ((NULL == g_pSetDeviceGammaRamp) ||
15064 (NULL == g_pGetDeviceGammaRamp)) {
15065 FreeLibrary(hGDI32DLL);
15066 hGDI32DLL = NULL;
15067 return 0;
15068 }
15069 }
15070 }
15071
15072 HDC hDC = GetDC(NULL); // Get the full screen DC
15073
15074 /*
15075 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15076 if (cmcap != CM_GAMMA_RAMP)
15077 {
15078 wxLogMessage(" Video hardware does not support brightness control by
15079 gamma ramp adjustment."); return false;
15080 }
15081 */
15082
15083 int increment = brightness * 256 / 100;
15084
15085 // Build the Gamma Ramp table
15086 WORD GammaTable[3][256];
15087
15088 int table_val = 0;
15089 for (int i = 0; i < 256; i++) {
15090 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15091 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15092 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15093
15094 table_val += increment;
15095
15096 if (table_val > 65535) table_val = 65535;
15097 }
15098
15099 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15100 ReleaseDC(NULL, hDC); // Release the DC
15101
15102 return 1;
15103 }
15104#endif
15105
15106 {
15107 if (g_pSavedGammaMap) {
15108 HDC hDC = GetDC(NULL); // Get the full screen DC
15109 g_pSetDeviceGammaRamp(hDC,
15110 g_pSavedGammaMap); // Restore the saved ramp table
15111 ReleaseDC(NULL, hDC); // Release the DC
15112 }
15113
15114 if (brightness < 100) {
15115 if (NULL == g_pcurtain) InitScreenBrightness();
15116
15117 if (g_pcurtain) {
15118 int sbrite = wxMax(1, brightness);
15119 sbrite = wxMin(100, sbrite);
15120
15121 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15122 }
15123 } else {
15124 if (g_pcurtain) {
15125 g_pcurtain->Close();
15126 g_pcurtain->Destroy();
15127 g_pcurtain = NULL;
15128 }
15129 }
15130
15131 return 1;
15132 }
15133
15134#endif
15135
15136#ifdef BRIGHT_XCALIB
15137
15138 if (!g_brightness_init) {
15139 last_brightness = 100;
15140 g_brightness_init = true;
15141 temp_file_name = wxFileName::CreateTempFileName("");
15142 InitScreenBrightness();
15143 }
15144
15145#ifdef __OPCPN_USEICC__
15146 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15147 // desired, and then activate this temporary profile using xcalib <filename>
15148 if (!CreateSimpleICCProfileFile(
15149 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15150 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15151 wxString cmd("xcalib ");
15152 cmd += temp_file_name;
15153
15154 wxExecute(cmd, wxEXEC_ASYNC);
15155 }
15156
15157#else
15158 // Or, use "xcalib -co" to set overall contrast value
15159 // This is not as nice, since the -co parameter wants to be a fraction of
15160 // the current contrast, and values greater than 100 are not allowed. As a
15161 // result, increases of contrast must do a "-clear" step first, which
15162 // produces objectionable flashing.
15163 if (brightness > last_brightness) {
15164 wxString cmd;
15165 cmd = "xcalib -clear";
15166 wxExecute(cmd, wxEXEC_ASYNC);
15167
15168 ::wxMilliSleep(10);
15169
15170 int brite_adj = wxMax(1, brightness);
15171 cmd.Printf("xcalib -co %2d -a", brite_adj);
15172 wxExecute(cmd, wxEXEC_ASYNC);
15173 } else {
15174 int brite_adj = wxMax(1, brightness);
15175 int factor = (brite_adj * 100) / last_brightness;
15176 factor = wxMax(1, factor);
15177 wxString cmd;
15178 cmd.Printf("xcalib -co %2d -a", factor);
15179 wxExecute(cmd, wxEXEC_ASYNC);
15180 }
15181
15182#endif
15183
15184 last_brightness = brightness;
15185
15186#endif
15187
15188 return 0;
15189}
15190
15191#ifdef __OPCPN_USEICC__
15192
15193#define MLUT_TAG 0x6d4c5554L
15194#define VCGT_TAG 0x76636774L
15195
15196int GetIntEndian(unsigned char *s) {
15197 int ret;
15198 unsigned char *p;
15199 int i;
15200
15201 p = (unsigned char *)&ret;
15202
15203 if (1)
15204 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15205 else
15206 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15207
15208 return ret;
15209}
15210
15211unsigned short GetShortEndian(unsigned char *s) {
15212 unsigned short ret;
15213 unsigned char *p;
15214 int i;
15215
15216 p = (unsigned char *)&ret;
15217
15218 if (1)
15219 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15220 else
15221 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15222
15223 return ret;
15224}
15225
15226// Create a very simple Gamma correction file readable by xcalib
15227int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15228 double co_green, double co_blue) {
15229 FILE *fp;
15230
15231 if (file_name) {
15232 fp = fopen(file_name, "wb");
15233 if (!fp) return -1; /* file can not be created */
15234 } else
15235 return -1; /* filename char pointer not valid */
15236
15237 // Write header
15238 char header[128];
15239 for (int i = 0; i < 128; i++) header[i] = 0;
15240
15241 fwrite(header, 128, 1, fp);
15242
15243 // Num tags
15244 int numTags0 = 1;
15245 int numTags = GetIntEndian((unsigned char *)&numTags0);
15246 fwrite(&numTags, 1, 4, fp);
15247
15248 int tagName0 = VCGT_TAG;
15249 int tagName = GetIntEndian((unsigned char *)&tagName0);
15250 fwrite(&tagName, 1, 4, fp);
15251
15252 int tagOffset0 = 128 + 4 * sizeof(int);
15253 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15254 fwrite(&tagOffset, 1, 4, fp);
15255
15256 int tagSize0 = 1;
15257 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15258 fwrite(&tagSize, 1, 4, fp);
15259
15260 fwrite(&tagName, 1, 4, fp); // another copy of tag
15261
15262 fwrite(&tagName, 1, 4, fp); // dummy
15263
15264 // Table type
15265
15266 /* VideoCardGammaTable (The simplest type) */
15267 int gammatype0 = 0;
15268 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15269 fwrite(&gammatype, 1, 4, fp);
15270
15271 int numChannels0 = 3;
15272 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15273 fwrite(&numChannels, 1, 2, fp);
15274
15275 int numEntries0 = 256;
15276 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15277 fwrite(&numEntries, 1, 2, fp);
15278
15279 int entrySize0 = 1;
15280 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15281 fwrite(&entrySize, 1, 2, fp);
15282
15283 unsigned char ramp[256];
15284
15285 // Red ramp
15286 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15287 fwrite(ramp, 256, 1, fp);
15288
15289 // Green ramp
15290 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15291 fwrite(ramp, 256, 1, fp);
15292
15293 // Blue ramp
15294 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15295 fwrite(ramp, 256, 1, fp);
15296
15297 fclose(fp);
15298
15299 return 0;
15300}
15301#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.
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:1310
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1309
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1310
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1309
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:13664
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:4518
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11708
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4514
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3612
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13623
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:4464
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:781
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:484
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:515
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2338
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:7949
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7758
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5046
float GetVPScale()
Return the ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:472
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:872
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4595
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5326
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:765
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4539
bool IsTideDialogOpen() const
Definition chcanv.cpp:13662
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:4601
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13619
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4459
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5345
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10118
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.