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