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// For compilers that support precompilation, includes "wx.h".
27#include <wx/wxprec.h>
28
29#ifndef WX_PRECOMP
30#include <wx/wx.h>
31#endif // precompiled headers
32#include <wx/image.h>
33#include <wx/graphics.h>
34#include <wx/clipbrd.h>
35#include <wx/aui/aui.h>
36
37#include "config.h"
38
39#include "o_sound/o_sound.h"
40
41#include "model/ais_decoder.h"
44#include "model/cmdline.h"
45#include "model/conn_params.h"
46#include "model/geodesic.h"
47#include "model/gui.h"
48#include "model/gui_vars.h"
49#include "model/idents.h"
50#include "model/multiplexer.h"
52#include "model/nav_object_database.h"
53#include "model/navobj_db.h"
54#include "model/navutil_base.h"
55#include "model/own_ship.h"
56#include "model/plugin_comm.h"
57#include "model/route.h"
58#include "model/routeman.h"
59#include "model/select.h"
60#include "model/select_item.h"
61#include "model/track.h"
62
63#include "ais.h"
66#include "canvas_config.h"
67#include "canvas_menu.h"
68#include "canvas_options.h"
69#include "chartdb.h"
70#include "chartimg.h"
71#include "chcanv.h"
72#include "ch_info_win.h"
73#include "cm93.h" // for chart outline draw
74#include "compass.h"
75#include "concanv.h"
76#include "detail_slider.h"
77#include "hotkeys_dlg.h"
78#include "font_mgr.h"
79#include "gl_texture_descr.h"
80#include "go_to_position_dlg.h"
81#include "gshhs.h"
82#include "ienc_toolbar.h"
83#include "kml.h"
84#include "line_clip.h"
85#include "mark_info.h"
86#include "mbtiles.h"
87#include "mui_bar.h"
88#include "navutil.h"
89#include "ocpn_aui_manager.h"
90#include "ocpndc.h"
91#include "ocpn_frame.h"
92#include "ocpn_pixel.h"
93#include "ocpn_region.h"
94#include "options.h"
95#include "piano.h"
96#include "pluginmanager.h"
97#include "quilt.h"
98#include "route_gui.h"
99#include "routemanagerdialog.h"
100#include "route_point_gui.h"
101#include "route_prop_dlg_impl.h"
102#include "s52plib.h"
103#include "s52utils.h"
104#include "s57_query_dlg.h"
105#include "s57chart.h" // for ArrayOfS57Obj
106#include "shapefile_basemap.h"
107#include "styles.h"
108#include "tcmgr.h"
109#include "tc_win.h"
110#include "thumbwin.h"
111#include "tide_time.h"
112#include "timers.h"
113#include "toolbar.h"
114#include "track_gui.h"
115#include "track_prop_dlg.h"
116#include "undo.h"
117
118#include "s57_ocpn_utils.h"
119
120#ifdef __ANDROID__
121#include "androidUTIL.h"
122#endif
123
124#ifdef ocpnUSE_GL
125#include "gl_chart_canvas.h"
128#endif
129
130#ifdef __VISUALC__
131#include <wx/msw/msvcrt.h>
132#endif
133
134#ifndef __WXMSW__
135#include <signal.h>
136#include <setjmp.h>
137#endif
138
139#ifdef __WXMSW__
140#define printf printf2
141
142int __cdecl printf2(const char *format, ...) {
143 char str[1024];
144
145 va_list argptr;
146 va_start(argptr, format);
147 int ret = vsnprintf(str, sizeof(str), format, argptr);
148 va_end(argptr);
149 OutputDebugStringA(str);
150 return ret;
151}
152#endif
153
154#if defined(__MSVC__) && (_MSC_VER < 1700)
155#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
156#endif
157
158// Define to enable the invocation of a temporary menubar by pressing the Alt
159// key. Not implemented for Windows XP, as it interferes with Alt-Tab
160// processing.
161#define OCPN_ALT_MENUBAR 1
162
163// Profiling support
164// #include "/usr/include/valgrind/callgrind.h"
165
166// ----------------------------------------------------------------------------
167// Useful Prototypes
168// ----------------------------------------------------------------------------
169extern ColorScheme global_color_scheme; // library dependence
170extern wxColor GetDimColor(wxColor c); // library dependence
171
172static bool g_bSmoothRecenter = true;
173static bool bDrawCurrentValues;
183static int mouse_x;
193static int mouse_y;
194static bool mouse_leftisdown;
195static bool g_brouteCreating;
196static int r_gamma_mult;
197static int g_gamma_mult;
198static int b_gamma_mult;
199static int gamma_state;
200static bool g_brightness_init;
201static int last_brightness;
202static wxGLContext *g_pGLcontext; // shared common context
203
204// "Curtain" mode parameters
205static wxDialog *g_pcurtain;
206
207static wxString g_lastS52PLIBPluginMessage;
208
209#define MIN_BRIGHT 10
210#define MAX_BRIGHT 100
211
212//------------------------------------------------------------------------------
213// ChartCanvas Implementation
214//------------------------------------------------------------------------------
215BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
216EVT_PAINT(ChartCanvas::OnPaint)
217EVT_ACTIVATE(ChartCanvas::OnActivate)
218EVT_SIZE(ChartCanvas::OnSize)
219#ifndef HAVE_WX_GESTURE_EVENTS
220EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
221#endif
222EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
223EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
224EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
225EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
226EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
227EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
228EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
229EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
230EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
231EVT_KEY_UP(ChartCanvas::OnKeyUp)
232EVT_CHAR(ChartCanvas::OnKeyChar)
233EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
234EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
235EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
236EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
237EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
238EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
239EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
240EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
241EVT_TIMER(MENU_TIMER, ChartCanvas::OnMenuTimer)
242EVT_TIMER(TAP_TIMER, ChartCanvas::OnTapTimer)
243
244END_EVENT_TABLE()
245
246// Define a constructor for my canvas
247ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
248 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
249 m_nmea_log(nmea_log) {
250 parent_frame = (MyFrame *)frame; // save a pointer to parent
251 m_canvasIndex = canvasIndex;
252
253 pscratch_bm = NULL;
254
255 SetBackgroundColour(wxColour(0, 0, 0));
256 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
257 // color scheme change
258
259 m_groupIndex = 0;
260 m_bDrawingRoute = false;
261 m_bRouteEditing = false;
262 m_bMarkEditing = false;
263 m_bRoutePoinDragging = false;
264 m_bIsInRadius = false;
265 m_bMayToggleMenuBar = true;
266
267 m_bFollow = false;
268 m_bShowNavobjects = true;
269 m_bTCupdate = false;
270 m_bAppendingRoute = false; // was true in MSW, why??
271 pThumbDIBShow = NULL;
272 m_bShowCurrent = false;
273 m_bShowTide = false;
274 bShowingCurrent = false;
275 pCwin = NULL;
276 warp_flag = false;
277 m_bzooming = false;
278 m_b_paint_enable = true;
279 m_routeState = 0;
280
281 pss_overlay_bmp = NULL;
282 pss_overlay_mask = NULL;
283 m_bChartDragging = false;
284 m_bMeasure_Active = false;
285 m_bMeasure_DistCircle = false;
286 m_pMeasureRoute = NULL;
287 m_pTrackRolloverWin = NULL;
288 m_pRouteRolloverWin = NULL;
289 m_pAISRolloverWin = NULL;
290 m_bedge_pan = false;
291 m_disable_edge_pan = false;
292 m_dragoffsetSet = false;
293 m_bautofind = false;
294 m_bFirstAuto = true;
295 m_groupIndex = 0;
296 m_singleChart = NULL;
297 m_upMode = NORTH_UP_MODE;
298 m_bShowAIS = true;
299 m_bShowAISScaled = false;
300 m_timed_move_vp_active = false;
301 m_inPinch = false;
302 m_disable_adjust_on_zoom = false;
303
304 m_vLat = 0.;
305 m_vLon = 0.;
306
307 m_pCIWin = NULL;
308
309 m_pSelectedRoute = NULL;
310 m_pSelectedTrack = NULL;
311 m_pRoutePointEditTarget = NULL;
312 m_pFoundPoint = NULL;
313 m_pMouseRoute = NULL;
314 m_prev_pMousePoint = NULL;
315 m_pEditRouteArray = NULL;
316 m_pFoundRoutePoint = NULL;
317 m_FinishRouteOnKillFocus = true;
318
319 m_pRolloverRouteSeg = NULL;
320 m_pRolloverTrackSeg = NULL;
321 m_bsectors_shown = false;
322
323 m_bbrightdir = false;
324 r_gamma_mult = 1;
325 g_gamma_mult = 1;
326 b_gamma_mult = 1;
327
328 m_pos_image_user_day = NULL;
329 m_pos_image_user_dusk = NULL;
330 m_pos_image_user_night = NULL;
331 m_pos_image_user_grey_day = NULL;
332 m_pos_image_user_grey_dusk = NULL;
333 m_pos_image_user_grey_night = NULL;
334
335 m_zoom_factor = 1;
336 m_rotation_speed = 0;
337 m_mustmove = 0;
338
339 m_OSoffsetx = 0.;
340 m_OSoffsety = 0.;
341
342 m_pos_image_user_yellow_day = NULL;
343 m_pos_image_user_yellow_dusk = NULL;
344 m_pos_image_user_yellow_night = NULL;
345
346 SetOwnShipState(SHIP_INVALID);
347
348 undo = new Undo(this);
349
350 VPoint.Invalidate();
351
352 m_glcc = NULL;
353
354 m_focus_indicator_pix = 1;
355
356 m_pCurrentStack = NULL;
357 m_bpersistent_quilt = false;
358 m_piano_ctx_menu = NULL;
359 m_Compass = NULL;
360 m_NotificationsList = NULL;
361 m_notification_button = NULL;
362
363 g_ChartNotRenderScaleFactor = 2.0;
364 m_bShowScaleInStatusBar = true;
365
366 m_muiBar = NULL;
367 m_bShowScaleInStatusBar = false;
368 m_show_focus_bar = true;
369
370 m_bShowOutlines = false;
371 m_bDisplayGrid = false;
372 m_bShowDepthUnits = true;
373 m_encDisplayCategory = (int)STANDARD;
374
375 m_encShowLights = true;
376 m_encShowAnchor = true;
377 m_encShowDataQual = false;
378 m_bShowGPS = true;
379 m_pQuilt = new Quilt(this);
380 SetQuiltMode(true);
381 SetAlertString("");
382 m_sector_glat = 0;
383 m_sector_glon = 0;
384 g_PrintingInProgress = false;
385
386#ifdef HAVE_WX_GESTURE_EVENTS
387 m_oldVPSScale = -1.0;
388 m_popupWanted = false;
389 m_leftdown = false;
390#endif /* HAVE_WX_GESTURE_EVENTS */
391 m_inLongPress = false;
392 m_sw_down_time = 0;
393 m_sw_up_time = 0;
394 m_sw_left_down.Start();
395 m_sw_left_up.Start();
396
397 SetupGlCanvas();
398
399 singleClickEventIsValid = false;
400
401 // Build the cursors
402
403 pCursorLeft = NULL;
404 pCursorRight = NULL;
405 pCursorUp = NULL;
406 pCursorDown = NULL;
407 pCursorArrow = NULL;
408 pCursorPencil = NULL;
409 pCursorCross = NULL;
410
411 RebuildCursors();
412
413 SetCursor(*pCursorArrow);
414
415 pPanTimer = new wxTimer(this, m_MouseDragging);
416 pPanTimer->Stop();
417
418 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
419 pMovementTimer->Stop();
420
421 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
422 pMovementStopTimer->Stop();
423
424 pRotDefTimer = new wxTimer(this, ROT_TIMER);
425 pRotDefTimer->Stop();
426
427 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
428 m_DoubleClickTimer->Stop();
429
430 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
431 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
432 m_chart_drag_inertia_active = false;
433
434 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
435 m_animationActive = false;
436 m_menuTimer.SetOwner(this, MENU_TIMER);
437 m_tap_timer.SetOwner(this, TAP_TIMER);
438
439 m_panx = m_pany = 0;
440 m_panspeed = 0;
441 m_panx_target_final = m_pany_target_final = 0;
442 m_panx_target_now = m_pany_target_now = 0;
443
444 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
445 pCurTrackTimer->Stop();
446 m_curtrack_timer_msec = 10;
447
448 m_wheelzoom_stop_oneshot = 0;
449 m_last_wheel_dir = 0;
450
451 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
452
453 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
454
455 m_rollover_popup_timer_msec = 20;
456
457 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
458
459 m_b_rot_hidef = true;
460
461 proute_bm = NULL;
462 m_prot_bm = NULL;
463
464 m_upMode = NORTH_UP_MODE;
465 m_bLookAhead = false;
466
467 // Set some benign initial values
468
469 m_cs = GLOBAL_COLOR_SCHEME_DAY;
470 VPoint.clat = 0;
471 VPoint.clon = 0;
472 VPoint.view_scale_ppm = 1;
473 VPoint.Invalidate();
474 m_nMeasureState = 0;
475
476 m_canvas_scale_factor = 1.;
477
478 m_canvas_width = 1000;
479
480 m_overzoomTextWidth = 0;
481 m_overzoomTextHeight = 0;
482
483 // Create the default world chart
484 pWorldBackgroundChart = new GSHHSChart;
485 gShapeBasemap.Reset();
486
487 // Create the default depth unit emboss maps
488 m_pEM_Feet = NULL;
489 m_pEM_Meters = NULL;
490 m_pEM_Fathoms = NULL;
491
492 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
493
494 m_pEM_OverZoom = NULL;
495 SetOverzoomFont();
496 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
497
498 // Build icons for tide/current points
499 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
500 m_bmTideDay =
501 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
502
503 // Dusk
504 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
505
506 // Night
507 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
508
509 // Build Dusk/Night ownship icons
510 double factor_dusk = 0.5;
511 double factor_night = 0.25;
512
513 // Red
514 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
515
516 int rimg_width = m_os_image_red_day.GetWidth();
517 int rimg_height = m_os_image_red_day.GetHeight();
518
519 m_os_image_red_dusk = m_os_image_red_day.Copy();
520 m_os_image_red_night = m_os_image_red_day.Copy();
521
522 for (int iy = 0; iy < rimg_height; iy++) {
523 for (int ix = 0; ix < rimg_width; ix++) {
524 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
525 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
526 m_os_image_red_day.GetGreen(ix, iy),
527 m_os_image_red_day.GetBlue(ix, iy));
528 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
529 hsv.value = hsv.value * factor_dusk;
530 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
531 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
532
533 hsv = wxImage::RGBtoHSV(rgb);
534 hsv.value = hsv.value * factor_night;
535 nrgb = wxImage::HSVtoRGB(hsv);
536 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
537 }
538 }
539 }
540
541 // Grey
542 m_os_image_grey_day =
543 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
544
545 int gimg_width = m_os_image_grey_day.GetWidth();
546 int gimg_height = m_os_image_grey_day.GetHeight();
547
548 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
549 m_os_image_grey_night = m_os_image_grey_day.Copy();
550
551 for (int iy = 0; iy < gimg_height; iy++) {
552 for (int ix = 0; ix < gimg_width; ix++) {
553 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
554 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
555 m_os_image_grey_day.GetGreen(ix, iy),
556 m_os_image_grey_day.GetBlue(ix, iy));
557 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
558 hsv.value = hsv.value * factor_dusk;
559 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
560 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
561
562 hsv = wxImage::RGBtoHSV(rgb);
563 hsv.value = hsv.value * factor_night;
564 nrgb = wxImage::HSVtoRGB(hsv);
565 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
566 }
567 }
568 }
569
570 // Yellow
571 m_os_image_yellow_day = m_os_image_red_day.Copy();
572
573 gimg_width = m_os_image_yellow_day.GetWidth();
574 gimg_height = m_os_image_yellow_day.GetHeight();
575
576 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
577 m_os_image_yellow_night = m_os_image_red_day.Copy();
578
579 for (int iy = 0; iy < gimg_height; iy++) {
580 for (int ix = 0; ix < gimg_width; ix++) {
581 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
582 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
583 m_os_image_yellow_day.GetGreen(ix, iy),
584 m_os_image_yellow_day.GetBlue(ix, iy));
585 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
586 hsv.hue += 60. / 360.; // shift to yellow
587 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
588 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
589
590 hsv = wxImage::RGBtoHSV(rgb);
591 hsv.value = hsv.value * factor_dusk;
592 hsv.hue += 60. / 360.; // shift to yellow
593 nrgb = wxImage::HSVtoRGB(hsv);
594 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
595
596 hsv = wxImage::RGBtoHSV(rgb);
597 hsv.hue += 60. / 360.; // shift to yellow
598 hsv.value = hsv.value * factor_night;
599 nrgb = wxImage::HSVtoRGB(hsv);
600 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
601 }
602 }
603 }
604
605 // Set initial pointers to ownship images
606 m_pos_image_red = &m_os_image_red_day;
607 m_pos_image_yellow = &m_os_image_yellow_day;
608 m_pos_image_grey = &m_os_image_grey_day;
609
610 SetUserOwnship();
611
612 m_pBrightPopup = NULL;
613
614#ifdef ocpnUSE_GL
615 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
616#endif
617
618 SetupGridFont();
619
620 m_Piano = new Piano(this);
621
622 m_bShowCompassWin = true;
623 m_Compass = new ocpnCompass(this);
624 m_Compass->SetScaleFactor(g_compass_scalefactor);
625 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
626
627 m_notification_button = new NotificationButton(this);
628 m_notification_button->SetScaleFactor(g_compass_scalefactor);
629 m_notification_button->Show(true);
630
631 m_pianoFrozen = false;
632
633 SetMinSize(wxSize(200, 200));
634
635 m_displayScale = 1.0;
636#if defined(__WXOSX__) || defined(__WXGTK3__)
637 // Support scaled HDPI displays.
638 m_displayScale = GetContentScaleFactor();
639#endif
640 VPoint.SetPixelScale(m_displayScale);
641
642#ifdef HAVE_WX_GESTURE_EVENTS
643 // if (!m_glcc)
644 {
645 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
646 wxLogError("Failed to enable touch events");
647 }
648
649 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
650
651 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
652 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
653
654 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
655 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
656
657 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
658 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
659
660 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
661 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
662 }
663#endif
664
665 // Listen for notification events
666 auto &noteman = NotificationManager::GetInstance();
667
668 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
669 evt_notificationlist_change_listener.Listen(
670 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
671 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
672 if (m_NotificationsList && m_NotificationsList->IsShown()) {
673 m_NotificationsList->ReloadNotificationList();
674 }
675 Refresh();
676 });
677}
678
679ChartCanvas::~ChartCanvas() {
680 delete pThumbDIBShow;
681
682 // Delete Cursors
683 delete pCursorLeft;
684 delete pCursorRight;
685 delete pCursorUp;
686 delete pCursorDown;
687 delete pCursorArrow;
688 delete pCursorPencil;
689 delete pCursorCross;
690
691 delete pPanTimer;
692 delete pMovementTimer;
693 delete pMovementStopTimer;
694 delete pCurTrackTimer;
695 delete pRotDefTimer;
696 delete m_DoubleClickTimer;
697
698 delete m_pTrackRolloverWin;
699 delete m_pRouteRolloverWin;
700 delete m_pAISRolloverWin;
701 delete m_pBrightPopup;
702
703 delete m_pCIWin;
704
705 delete pscratch_bm;
706
707 m_dc_route.SelectObject(wxNullBitmap);
708 delete proute_bm;
709
710 delete pWorldBackgroundChart;
711 delete pss_overlay_bmp;
712
713 delete m_pEM_Feet;
714 delete m_pEM_Meters;
715 delete m_pEM_Fathoms;
716
717 delete m_pEM_OverZoom;
718 // delete m_pEM_CM93Offset;
719
720 delete m_prot_bm;
721
722 delete m_pos_image_user_day;
723 delete m_pos_image_user_dusk;
724 delete m_pos_image_user_night;
725 delete m_pos_image_user_grey_day;
726 delete m_pos_image_user_grey_dusk;
727 delete m_pos_image_user_grey_night;
728 delete m_pos_image_user_yellow_day;
729 delete m_pos_image_user_yellow_dusk;
730 delete m_pos_image_user_yellow_night;
731
732 delete undo;
733#ifdef ocpnUSE_GL
734 if (!g_bdisable_opengl) {
735 delete m_glcc;
736
737#if wxCHECK_VERSION(2, 9, 0)
738 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
739#endif
740 }
741#endif
742
743 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
744 // wx tries to deliver events to this canvas during destroy.
745 MUIBar *muiBar = m_muiBar;
746 m_muiBar = 0;
747 delete muiBar;
748 delete m_pQuilt;
749 delete m_pCurrentStack;
750 delete m_Compass;
751 delete m_Piano;
752}
753
754void ChartCanvas::SetupGridFont() {
755 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
756 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
757 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
758 m_pgridFont = FontMgr::Get().FindOrCreateFont(
759 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
760 FALSE, wxString("Arial"));
761}
762
763void ChartCanvas::RebuildCursors() {
764 delete pCursorLeft;
765 delete pCursorRight;
766 delete pCursorUp;
767 delete pCursorDown;
768 delete pCursorArrow;
769 delete pCursorPencil;
770 delete pCursorCross;
771
772 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
773 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
774
775 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
776
777 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
778 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
779 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
780 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
781 wxImage ICursorPencil =
782 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
783 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
784
785#if !defined(__WXMSW__) && !defined(__WXQT__)
786 ICursorLeft.ConvertAlphaToMask(128);
787 ICursorRight.ConvertAlphaToMask(128);
788 ICursorUp.ConvertAlphaToMask(128);
789 ICursorDown.ConvertAlphaToMask(128);
790 ICursorPencil.ConvertAlphaToMask(10);
791 ICursorCross.ConvertAlphaToMask(10);
792#endif
793
794 if (ICursorLeft.Ok()) {
795 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
796 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
797 pCursorLeft = new wxCursor(ICursorLeft);
798 } else
799 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
800
801 if (ICursorRight.Ok()) {
802 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
803 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
804 pCursorRight = new wxCursor(ICursorRight);
805 } else
806 pCursorRight = new wxCursor(wxCURSOR_ARROW);
807
808 if (ICursorUp.Ok()) {
809 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
810 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
811 pCursorUp = new wxCursor(ICursorUp);
812 } else
813 pCursorUp = new wxCursor(wxCURSOR_ARROW);
814
815 if (ICursorDown.Ok()) {
816 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
817 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
818 pCursorDown = new wxCursor(ICursorDown);
819 } else
820 pCursorDown = new wxCursor(wxCURSOR_ARROW);
821
822 if (ICursorPencil.Ok()) {
823 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
824 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
825 pCursorPencil = new wxCursor(ICursorPencil);
826 } else
827 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
828
829 if (ICursorCross.Ok()) {
830 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
831 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
832 pCursorCross = new wxCursor(ICursorCross);
833 } else
834 pCursorCross = new wxCursor(wxCURSOR_ARROW);
835
836 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
837 pPlugIn_Cursor = NULL;
838}
839
840void ChartCanvas::CanvasApplyLocale() {
841 CreateDepthUnitEmbossMaps(m_cs);
842 CreateOZEmbossMapData(m_cs);
843}
844
845void ChartCanvas::SetupGlCanvas() {
846#ifndef __ANDROID__
847#ifdef ocpnUSE_GL
848 if (!g_bdisable_opengl) {
849 if (g_bopengl) {
850 wxLogMessage("Creating glChartCanvas");
851 m_glcc = new glChartCanvas(this);
852
853 // We use one context for all GL windows, so that textures etc will be
854 // automatically shared
855 if (IsPrimaryCanvas()) {
856 // qDebug() << "Creating Primary Context";
857
858 // wxGLContextAttrs ctxAttr;
859 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
860 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
861 // NULL, &ctxAttr);
862 wxGLContext *pctx = new wxGLContext(m_glcc);
863 m_glcc->SetContext(pctx);
864 g_pGLcontext = pctx; // Save a copy of the common context
865 } else {
866#ifdef __WXOSX__
867 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
868#else
869 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
870 // saved common context
871#endif
872 }
873 }
874 }
875#endif
876#endif
877
878#ifdef __ANDROID__ // ocpnUSE_GL
879 if (!g_bdisable_opengl) {
880 if (g_bopengl) {
881 // qDebug() << "SetupGlCanvas";
882 wxLogMessage("Creating glChartCanvas");
883
884 // We use one context for all GL windows, so that textures etc will be
885 // automatically shared
886 if (IsPrimaryCanvas()) {
887 qDebug() << "Creating Primary glChartCanvas";
888
889 // wxGLContextAttrs ctxAttr;
890 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
891 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
892 // NULL, &ctxAttr);
893 m_glcc = new glChartCanvas(this);
894
895 wxGLContext *pctx = new wxGLContext(m_glcc);
896 m_glcc->SetContext(pctx);
897 g_pGLcontext = pctx; // Save a copy of the common context
898 m_glcc->m_pParentCanvas = this;
899 // m_glcc->Reparent(this);
900 } else {
901 qDebug() << "Creating Secondary glChartCanvas";
902 // QGLContext *pctx =
903 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
904 // << "pctx: " << pctx;
905
906 m_glcc = new glChartCanvas(
907 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
908 // m_glcc = new glChartCanvas(this, pctx); //Shared
909 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
910 wxGLContext *pwxctx = new wxGLContext(m_glcc);
911 m_glcc->SetContext(pwxctx);
912 m_glcc->m_pParentCanvas = this;
913 // m_glcc->Reparent(this);
914 }
915 }
916 }
917#endif
918}
919
920void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
921 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
922
923 // On Android, we get a KillFocus on just about every keystroke.
924 // Why?
925#ifdef __ANDROID__
926 return;
927#endif
928
929 // Special logic:
930 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
931 // canvas focus. Why??? Who knows... So, we provide for this case by
932 // starting a timer if required to actually Finish() a route on a legitimate
933 // focus change, but not if the focus is quickly regained ( <20 msec.) on
934 // this canvas.
935#ifdef __WXOSX__
936 if (m_routeState && m_FinishRouteOnKillFocus)
937 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
938#else
939 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
940#endif
941}
942
943void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
944 m_routeFinishTimer.Stop();
945
946 // Try to keep the global top-line menubar selections up to date with the
947 // current "focus" canvas
948 gFrame->UpdateGlobalMenuItems(this);
949
950 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
951}
952
953void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
954 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
955}
956
957#ifdef HAVE_WX_GESTURE_EVENTS
958void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
959#ifdef __ANDROID__
960 /* we defer the popup menu call upon the leftup event
961 else the menu disappears immediately,
962 (see
963 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
964 */
965 m_popupWanted = true;
966#else
967 m_inLongPress = !g_bhide_context_menus;
968
969 // Send a synthetic mouse left-up event to sync the mouse pan logic.
970 m_menuPos = event.GetPosition();
971 wxMouseEvent ev(wxEVT_LEFT_UP);
972 ev.m_x = m_menuPos.x;
973 ev.m_y = m_menuPos.y;
974 wxPostEvent(this, ev);
975
976 // Send a "RIGHT CLICK" event, for plugins
977 wxMouseEvent ev_right_click(wxEVT_RIGHT_DOWN);
978 ev_right_click.m_x = m_menuPos.x;
979 ev_right_click.m_y = m_menuPos.y;
980 MouseEvent(ev_right_click);
981
982 // m_menuTimer.StartOnce(20); // Delay of 20 millisecond
983#endif
984}
985
986void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
987 // not implemented yet
988}
989
990void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
991
992void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
993
994void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
995#ifdef __WXGTK__
996 long dt = m_sw_left_up.Time() - m_sw_up_time;
997 m_sw_up_time = m_sw_left_up.Time();
998
999 // printf(" dt %ld\n",dt);
1000 if (dt < 5) {
1001 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1002 // ignore it.
1003 return;
1004 }
1005#endif
1006 // printf("Left_UP\n");
1007
1008 wxPoint pos = event.GetPosition();
1009
1010 m_leftdown = false;
1011
1012 if (!m_popupWanted) {
1013 wxMouseEvent ev(wxEVT_LEFT_UP);
1014 ev.m_x = pos.x;
1015 ev.m_y = pos.y;
1016 MouseEvent(ev);
1017 return;
1018 }
1019
1020 m_popupWanted = false;
1021
1022 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1023 ev.m_x = pos.x;
1024 ev.m_y = pos.y;
1025
1026 MouseEvent(ev);
1027}
1028
1029void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1030 m_leftdown = true;
1031
1032 // Detect and manage multiple left-downs coming from GTK mouse emulation
1033#ifdef __WXGTK__
1034 long dt = m_sw_left_down.Time() - m_sw_down_time;
1035 m_sw_down_time = m_sw_left_down.Time();
1036
1037 // printf("Left_DOWN_Entry: dt: %ld\n", dt);
1038
1039 if (dt < 5) {
1040 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1041 // ignore it.
1042 return;
1043 }
1044#endif
1045
1046 // printf("Left_DOWN\n");
1047
1048 // detect and manage double-tap
1049#ifdef __WXGTK__
1050 int max_double_click_distance = wxSystemSettings::GetMetric(wxSYS_DCLICK_X) *
1051 2; // Use system setting for distance
1052 wxRect tap_area(m_lastTapPos.x - max_double_click_distance,
1053 m_lastTapPos.y - max_double_click_distance,
1054 max_double_click_distance * 2, max_double_click_distance * 2);
1055
1056 // A new tap has started, check if it's close enough and in time
1057 if (m_tap_timer.IsRunning() && tap_area.Contains(event.GetPosition())) {
1058 // printf(" TapBump 1\n");
1059 m_tap_count += 1;
1060 } else {
1061 // printf(" TapSet 1\n");
1062 m_tap_count = 1;
1063 m_lastTapPos = event.GetPosition();
1064 m_tap_timer.StartOnce(
1065 350); //(wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC));
1066 }
1067
1068 if (m_tap_count == 2) {
1069 // printf(" Doubletap detected\n");
1070 m_tap_count = 0; // Reset after a double-tap
1071
1072 wxMouseEvent ev(wxEVT_LEFT_DCLICK);
1073 ev.m_x = event.m_x;
1074 ev.m_y = event.m_y;
1075 // wxPostEvent(this, ev);
1076 MouseEvent(ev);
1077 return;
1078 }
1079
1080#endif
1081
1082 MouseEvent(event);
1083}
1084
1085void ChartCanvas::OnMotion(wxMouseEvent &event) {
1086 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1087 dragging, upon simple click, and without the OnLeftDown event before Thus,
1088 this consists in skiping it, and setting the leftdown bit according to a
1089 status that we trust */
1090 event.m_leftDown = m_leftdown;
1091 MouseEvent(event);
1092}
1093
1094void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1095 /* there are spurious end zoom events upon right-click */
1096 if (event.IsGestureEnd()) return;
1097
1098 double factor = event.GetZoomFactor();
1099
1100 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1101 m_oldVPSScale = GetVPScale();
1102 }
1103
1104 double current_vps = GetVPScale();
1105 double wanted_factor = m_oldVPSScale / current_vps * factor;
1106
1107 ZoomCanvas(wanted_factor, true, false);
1108
1109 // Allow combined zoom/pan operation
1110 if (event.IsGestureStart()) {
1111 m_zoomStartPoint = event.GetPosition();
1112 } else {
1113 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1114 PanCanvas(-delta.x, -delta.y);
1115 m_zoomStartPoint = event.GetPosition();
1116 }
1117}
1118
1119void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1120
1121void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1122 DoRotateCanvas(0.0);
1123}
1124#endif /* HAVE_WX_GESTURE_EVENTS */
1125
1126void ChartCanvas::OnTapTimer(wxTimerEvent &event) {
1127 // printf("tap timer %d\n", m_tap_count);
1128 m_tap_count = 0;
1129}
1130
1131void ChartCanvas::OnMenuTimer(wxTimerEvent &event) {
1132 m_FinishRouteOnKillFocus = false;
1133 CallPopupMenu(m_menuPos.x, m_menuPos.y);
1134 m_FinishRouteOnKillFocus = true;
1135}
1136
1137void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1138 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1139 m_vLat = pcc->iLat;
1140 m_vLon = pcc->iLon;
1141
1142 m_restore_dbindex = pcc->DBindex;
1143 m_bFollow = pcc->bFollow;
1144 if (pcc->GroupID < 0) pcc->GroupID = 0;
1145
1146 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1147 m_groupIndex = 0;
1148 else
1149 m_groupIndex = pcc->GroupID;
1150
1151 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1152
1153 ShowTides(pcc->bShowTides);
1154 ShowCurrents(pcc->bShowCurrents);
1155
1156 SetShowDepthUnits(pcc->bShowDepthUnits);
1157 SetShowGrid(pcc->bShowGrid);
1158 SetShowOutlines(pcc->bShowOutlines);
1159
1160 SetShowAIS(pcc->bShowAIS);
1161 SetAttenAIS(pcc->bAttenAIS);
1162
1163 // ENC options
1164 SetShowENCText(pcc->bShowENCText);
1165 m_encDisplayCategory = pcc->nENCDisplayCategory;
1166 m_encShowDepth = pcc->bShowENCDepths;
1167 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1168 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1169 m_encShowLights = pcc->bShowENCLights;
1170 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1171 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1172 m_encShowDataQual = pcc->bShowENCDataQuality;
1173
1174 bool courseUp = pcc->bCourseUp;
1175 bool headUp = pcc->bHeadUp;
1176 m_upMode = NORTH_UP_MODE;
1177 if (courseUp)
1178 m_upMode = COURSE_UP_MODE;
1179 else if (headUp)
1180 m_upMode = HEAD_UP_MODE;
1181
1182 m_bLookAhead = pcc->bLookahead;
1183
1184 m_singleChart = NULL;
1185}
1186
1187void ChartCanvas::ApplyGlobalSettings() {
1188 // GPS compas window
1189 if (m_Compass) {
1190 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1191 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1192 }
1193 m_notification_button->UpdateStatus();
1194}
1195
1196void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1197 bool groupOK = CheckGroup(m_groupIndex);
1198
1199 if (!groupOK) {
1200 SetGroupIndex(m_groupIndex, true);
1201 }
1202}
1203
1204void ChartCanvas::SetShowGPS(bool bshow) {
1205 if (m_bShowGPS != bshow) {
1206 delete m_Compass;
1207 m_Compass = new ocpnCompass(this, bshow);
1208 m_Compass->SetScaleFactor(g_compass_scalefactor);
1209 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1210 }
1211 m_bShowGPS = bshow;
1212}
1213
1214void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1215 m_bShowCompassWin = bshow;
1216 if (m_Compass) {
1217 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1218 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1219 }
1220}
1221
1222int ChartCanvas::GetPianoHeight() {
1223 int height = 0;
1224 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1225
1226 return height;
1227}
1228
1229void ChartCanvas::ConfigureChartBar() {
1230 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1231
1232 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1233 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1234
1235 if (GetQuiltMode()) {
1236 m_Piano->SetRoundedRectangles(true);
1237 }
1238 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1239 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1240 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1241}
1242
1243void ChartCanvas::ShowTides(bool bShow) {
1244 gFrame->LoadHarmonics();
1245
1246 if (ptcmgr->IsReady()) {
1247 SetbShowTide(bShow);
1248
1249 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1250 } else {
1251 wxLogMessage("Chart1::Event...TCMgr Not Available");
1252 SetbShowTide(false);
1253 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1254 }
1255
1256 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1257 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1258
1259 // TODO
1260 // if( GetbShowTide() ) {
1261 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1262 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1263 // update
1264 // } else
1265 // FrameTCTimer.Stop();
1266}
1267
1268void ChartCanvas::ShowCurrents(bool bShow) {
1269 gFrame->LoadHarmonics();
1270
1271 if (ptcmgr->IsReady()) {
1272 SetbShowCurrent(bShow);
1273 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1274 } else {
1275 wxLogMessage("Chart1::Event...TCMgr Not Available");
1276 SetbShowCurrent(false);
1277 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1278 }
1279
1280 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1281 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1282
1283 // TODO
1284 // if( GetbShowCurrent() ) {
1285 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1286 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1287 // update
1288 // } else
1289 // FrameTCTimer.Stop();
1290}
1291
1292// TODO
1293static ChartDummy *pDummyChart;
1294
1297
1298void ChartCanvas::canvasRefreshGroupIndex() { SetGroupIndex(m_groupIndex); }
1299
1300void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1301 SetAlertString("");
1302
1303 int new_index = index;
1304 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1305
1306 bool bgroup_override = false;
1307 int old_group_index = new_index;
1308
1309 if (!CheckGroup(new_index)) {
1310 new_index = 0;
1311 bgroup_override = true;
1312 }
1313
1314 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1315 new_index = index;
1316
1317 // Get the currently displayed chart native scale, and the current ViewPort
1318 int current_chart_native_scale = GetCanvasChartNativeScale();
1319 ViewPort vp = GetVP();
1320
1321 m_groupIndex = new_index;
1322
1323 // Are there ENCs in this group
1324 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1325
1326 // Update the MUIBar for ENC availability
1327 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1328
1329 // Allow the chart database to pre-calculate the MBTile inclusion test
1330 // boolean...
1331 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1332
1333 // Invalidate the "sticky" chart on group change, since it might not be in
1334 // the new group
1335 g_sticky_chart = -1;
1336
1337 // We need a chartstack and quilt to figure out which chart to open in the
1338 // new group
1339 UpdateCanvasOnGroupChange();
1340
1341 int dbi_now = -1;
1342 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1343
1344 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1345
1346 // If a new reference chart is indicated, set a good scale for it.
1347 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1348 double best_scale = GetBestStartScale(dbi_hint, vp);
1349 SetVPScale(best_scale);
1350 }
1351
1352 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1353
1354 // Refresh the canvas, selecting the "best" chart,
1355 // applying the prior ViewPort exactly
1356 canvasChartsRefresh(dbi_hint);
1357
1358 UpdateCanvasControlBar();
1359
1360 if (!autoSwitch && bgroup_override) {
1361 // show a short timed message box
1362 wxString msg(_("Group \""));
1363
1364 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1365 msg += pGroup->m_group_name;
1366
1367 msg += _("\" is empty.");
1368
1369 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1370
1371 return;
1372 }
1373
1374 // Message box is deferred so that canvas refresh occurs properly before
1375 // dialog
1376 if (bgroup_override) {
1377 wxString msg(_("Group \""));
1378
1379 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1380 msg += pGroup->m_group_name;
1381
1382 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1383
1384 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1385 }
1386}
1387
1388bool ChartCanvas::CheckGroup(int igroup) {
1389 if (!ChartData) return true; // Not known yet...
1390
1391 if (igroup == 0) return true; // "all charts" is always OK
1392
1393 if (igroup < 0) // negative group is an error
1394 return false;
1395
1396 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1397
1398 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1399 // and auto-shift to group 0
1400 return false;
1401
1402 for (const auto &elem : pGroup->m_element_array) {
1403 for (unsigned int ic = 0;
1404 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1405 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1406 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1407
1408 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1409 }
1410 }
1411
1412 // If necessary, check for GSHHS
1413 for (const auto &elem : pGroup->m_element_array) {
1414 const wxString &element_root = elem.m_element_name;
1415 wxString test_string = "GSHH";
1416 if (element_root.Upper().Contains(test_string)) return true;
1417 }
1418
1419 return false;
1420}
1421
1422void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1423 if (!ChartData) return;
1424
1425 AbstractPlatform::ShowBusySpinner();
1426
1427 double old_scale = GetVPScale();
1428 InvalidateQuilt();
1429 SetQuiltRefChart(-1);
1430
1431 m_singleChart = NULL;
1432
1433 // delete m_pCurrentStack;
1434 // m_pCurrentStack = NULL;
1435
1436 // Build a new ChartStack
1437 if (!m_pCurrentStack) {
1438 m_pCurrentStack = new ChartStack;
1439 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1440 }
1441
1442 if (-1 != dbi_hint) {
1443 if (GetQuiltMode()) {
1444 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1445 SetQuiltRefChart(dbi_hint);
1446 } else {
1447 // Open the saved chart
1448 ChartBase *pTentative_Chart;
1449 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1450
1451 if (pTentative_Chart) {
1452 /* m_singleChart is always NULL here, (set above) should this go before
1453 * that? */
1454 if (m_singleChart) m_singleChart->Deactivate();
1455
1456 m_singleChart = pTentative_Chart;
1457 m_singleChart->Activate();
1458
1459 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1460 GetpCurrentStack(), m_singleChart->GetFullPath());
1461 }
1462 }
1463
1464 // refresh_Piano();
1465 } else {
1466 // Select reference chart from the stack, as though clicked by user
1467 // Make it the smallest scale chart on the stack
1468 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1469 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1470 SetQuiltRefChart(selected_index);
1471 }
1472
1473 // Validate the correct single chart, or set the quilt mode as appropriate
1474 SetupCanvasQuiltMode();
1475 if (!GetQuiltMode() && m_singleChart == 0) {
1476 // use a dummy like in DoChartUpdate
1477 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1478 m_singleChart = pDummyChart;
1479 SetVPScale(old_scale);
1480 }
1481
1482 ReloadVP();
1483
1484 UpdateCanvasControlBar();
1485 UpdateGPSCompassStatusBox(true);
1486
1487 SetCursor(wxCURSOR_ARROW);
1488
1489 AbstractPlatform::HideBusySpinner();
1490}
1491
1492bool ChartCanvas::DoCanvasUpdate() {
1493 double tLat, tLon; // Chart Stack location
1494 double vpLat, vpLon; // ViewPort location
1495 bool blong_jump = false;
1496 meters_to_shift = 0;
1497 dir_to_shift = 0;
1498
1499 bool bNewChart = false;
1500 bool bNewView = false;
1501 bool bCanvasChartAutoOpen = true; // debugging
1502
1503 bool bNewPiano = false;
1504 bool bOpenSpecified;
1505 ChartStack LastStack;
1506 ChartBase *pLast_Ch;
1507
1508 ChartStack WorkStack;
1509
1510 if (bDBUpdateInProgress) return false;
1511 if (!ChartData) return false;
1512
1513 if (ChartData->IsBusy()) return false;
1514 if (m_chart_drag_inertia_active) return false;
1515
1516 // Startup case:
1517 // Quilting is enabled, but the last chart seen was not quiltable
1518 // In this case, drop to single chart mode, set persistence flag,
1519 // And open the specified chart
1520 // TODO implement this
1521 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1522 // if( GetQuiltMode() ) {
1523 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1524 // gFrame->ToggleQuiltMode();
1525 // m_bpersistent_quilt = true;
1526 // m_singleChart = NULL;
1527 // }
1528 // }
1529 // }
1530
1531 // If in auto-follow mode, use the current glat,glon to build chart
1532 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1533 // other means
1534
1535 if (m_bFollow) {
1536 tLat = gLat;
1537 tLon = gLon;
1538
1539 // Set the ViewPort center based on the OWNSHIP offset
1540 double dx = m_OSoffsetx;
1541 double dy = m_OSoffsety;
1542 double d_east = dx / GetVP().view_scale_ppm;
1543 double d_north = dy / GetVP().view_scale_ppm;
1544
1545 if (GetUpMode() == NORTH_UP_MODE) {
1546 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1547 } else {
1548 double offset_angle = atan2(d_north, d_east);
1549 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1550 double chart_angle = GetVPRotation();
1551 double target_angle = chart_angle + offset_angle;
1552 double d_east_mod = offset_distance * cos(target_angle);
1553 double d_north_mod = offset_distance * sin(target_angle);
1554 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1555 }
1556
1557 // on lookahead mode, adjust the vp center point
1558 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1559 double cog_to_use = gCog;
1560 if (g_btenhertz &&
1561 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1562 cog_to_use = gCog_gt;
1563 blong_jump = true;
1564 }
1565 if (!g_btenhertz) cog_to_use = g_COGAvg;
1566
1567 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1568
1569 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1570 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1571
1572 double pixel_delta_tent =
1573 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1574
1575 double pixel_delta = 0;
1576
1577 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1578 // avoid jumping of the vp center point during slow maneuvering, or at
1579 // anchor....
1580 if (!std::isnan(gSog)) {
1581 if (gSog < 2.0)
1582 pixel_delta = 0.;
1583 else
1584 pixel_delta = pixel_delta_tent;
1585 }
1586
1587 meters_to_shift = 0;
1588 dir_to_shift = 0;
1589 if (!std::isnan(gCog)) {
1590 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1591 dir_to_shift = cog_to_use;
1592 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1593 &vpLon);
1594 } else {
1595 vpLat = gLat;
1596 vpLon = gLon;
1597 }
1598 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1599 m_OSoffsetx = 0; // center ownship on loss of GPS
1600 m_OSoffsety = 0;
1601 vpLat = gLat;
1602 vpLon = gLon;
1603 }
1604
1605 } else {
1606 tLat = m_vLat;
1607 tLon = m_vLon;
1608 vpLat = m_vLat;
1609 vpLon = m_vLon;
1610 }
1611
1612 if (GetQuiltMode()) {
1613 int current_db_index = -1;
1614 if (m_pCurrentStack)
1615 current_db_index =
1616 m_pCurrentStack
1617 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1618 // chart dbIndex
1619 else
1620 m_pCurrentStack = new ChartStack;
1621
1622 // This logic added to enable opening a chart when there is no
1623 // previous chart indication, either from inital startup, or from adding
1624 // new chart directory
1625 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1626 m_pCurrentStack) {
1627 if (m_pCurrentStack->nEntry) {
1628 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1629 1); // smallest scale
1630 SelectQuiltRefdbChart(new_dbIndex, true);
1631 m_bautofind = false;
1632 }
1633 }
1634
1635 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1636 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1637
1638 if (m_bFirstAuto) {
1639 // Allow the chart database to pre-calculate the MBTile inclusion test
1640 // boolean...
1641 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1642
1643 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1644 // physical pixels. On standard DPI displays where logical = physical
1645 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1646 // logical pixels, this ratio would be 0.5.
1647 double proposed_scale_onscreen =
1648 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1649
1650 int initial_db_index = m_restore_dbindex;
1651 if (initial_db_index < 0) {
1652 if (m_pCurrentStack->nEntry) {
1653 initial_db_index =
1654 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1655 } else
1656 m_bautofind = true; // initial_db_index = 0;
1657 }
1658
1659 if (m_pCurrentStack->nEntry) {
1660 int initial_type = ChartData->GetDBChartType(initial_db_index);
1661
1662 // Check to see if the target new chart is quiltable as a reference
1663 // chart
1664
1665 if (!IsChartQuiltableRef(initial_db_index)) {
1666 // If it is not quiltable, then walk the stack up looking for a
1667 // satisfactory chart i.e. one that is quiltable and of the same type
1668 // XXX if there's none?
1669 int stack_index = 0;
1670
1671 if (stack_index >= 0) {
1672 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1673 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1674 if (IsChartQuiltableRef(test_db_index) &&
1675 (initial_type ==
1676 ChartData->GetDBChartType(initial_db_index))) {
1677 initial_db_index = test_db_index;
1678 break;
1679 }
1680 stack_index++;
1681 }
1682 }
1683 }
1684
1685 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1686 if (pc) {
1687 SetQuiltRefChart(initial_db_index);
1688 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1689 }
1690 }
1691 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1692 // just GetVPScale(), so I'm not sure why it's necessary to define the
1693 // proposed_scale_onscreen variable.
1694 bNewView |= SetViewPoint(vpLat, vpLon,
1695 GetCanvasScaleFactor() / proposed_scale_onscreen,
1696 0, GetVPRotation());
1697 }
1698 // Measure rough jump distance if in bfollow mode
1699 // No good reason to do smooth pan for
1700 // jump distance more than one screen width at scale.
1701 bool super_jump = false;
1702 if (m_bFollow) {
1703 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1704 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1705 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1706 }
1707#if 0
1708 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead && !g_btouch && !m_bzooming) {
1709 int nstep = 5;
1710 if (blong_jump) nstep = 20;
1711 StartTimedMovementVP(vpLat, vpLon, nstep);
1712 } else
1713#endif
1714 {
1715 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1716 }
1717
1718 goto update_finish;
1719 }
1720
1721 // Single Chart Mode from here....
1722 pLast_Ch = m_singleChart;
1723 ChartTypeEnum new_open_type;
1724 ChartFamilyEnum new_open_family;
1725 if (pLast_Ch) {
1726 new_open_type = pLast_Ch->GetChartType();
1727 new_open_family = pLast_Ch->GetChartFamily();
1728 } else {
1729 new_open_type = CHART_TYPE_KAP;
1730 new_open_family = CHART_FAMILY_RASTER;
1731 }
1732
1733 bOpenSpecified = m_bFirstAuto;
1734
1735 // Make sure the target stack is valid
1736 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1737
1738 // Build a chart stack based on tLat, tLon
1739 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1740 m_groupIndex)) { // Bogus Lat, Lon?
1741 if (NULL == pDummyChart) {
1742 pDummyChart = new ChartDummy;
1743 bNewChart = true;
1744 }
1745
1746 if (m_singleChart)
1747 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1748
1749 m_singleChart = pDummyChart;
1750
1751 // If the current viewpoint is invalid, set the default scale to
1752 // something reasonable.
1753 double set_scale = GetVPScale();
1754 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1755
1756 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1757
1758 // If the chart stack has just changed, there is new status
1759 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1760 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1761 bNewPiano = true;
1762 bNewChart = true;
1763 }
1764 }
1765
1766 // Copy the new (by definition empty) stack into the target stack
1767 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1768
1769 goto update_finish;
1770 }
1771
1772 // Check to see if Chart Stack has changed
1773 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1774 // New chart stack, so...
1775 bNewPiano = true;
1776
1777 // Save a copy of the current stack
1778 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1779
1780 // Copy the new stack into the target stack
1781 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1782
1783 // Is Current Chart in new stack?
1784
1785 int tEntry = -1;
1786 if (NULL != m_singleChart) // this handles startup case
1787 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1788 m_singleChart->GetFullPath());
1789
1790 if (tEntry != -1) { // m_singleChart is in the new stack
1791 m_pCurrentStack->CurrentStackEntry = tEntry;
1792 bNewChart = false;
1793 }
1794
1795 else // m_singleChart is NOT in new stack
1796 { // So, need to open a new chart
1797 // Find the largest scale raster chart that opens OK
1798
1799 ChartBase *pProposed = NULL;
1800
1801 if (bCanvasChartAutoOpen) {
1802 bool search_direction =
1803 false; // default is to search from lowest to highest
1804 int start_index = 0;
1805
1806 // A special case: If panning at high scale, open largest scale
1807 // chart first
1808 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1809 (LastStack.nEntry == 0)) {
1810 search_direction = true;
1811 start_index = m_pCurrentStack->nEntry - 1;
1812 }
1813
1814 // Another special case, open specified index on program start
1815 if (bOpenSpecified) {
1816 search_direction = false;
1817 start_index = 0;
1818 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1819 start_index = 0;
1820
1821 new_open_type = CHART_TYPE_DONTCARE;
1822 }
1823
1824 pProposed = ChartData->OpenStackChartConditional(
1825 m_pCurrentStack, start_index, search_direction, new_open_type,
1826 new_open_family);
1827
1828 // Try to open other types/families of chart in some priority
1829 if (NULL == pProposed)
1830 pProposed = ChartData->OpenStackChartConditional(
1831 m_pCurrentStack, start_index, search_direction,
1832 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1833
1834 if (NULL == pProposed)
1835 pProposed = ChartData->OpenStackChartConditional(
1836 m_pCurrentStack, start_index, search_direction,
1837 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1838
1839 bNewChart = true;
1840
1841 } // bCanvasChartAutoOpen
1842
1843 else
1844 pProposed = NULL;
1845
1846 // If no go, then
1847 // Open a Dummy Chart
1848 if (NULL == pProposed) {
1849 if (NULL == pDummyChart) {
1850 pDummyChart = new ChartDummy;
1851 bNewChart = true;
1852 }
1853
1854 if (pLast_Ch)
1855 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1856
1857 pProposed = pDummyChart;
1858 }
1859
1860 // Arriving here, pProposed points to an opened chart, or NULL.
1861 if (m_singleChart) m_singleChart->Deactivate();
1862 m_singleChart = pProposed;
1863
1864 if (m_singleChart) {
1865 m_singleChart->Activate();
1866 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1867 m_pCurrentStack, m_singleChart->GetFullPath());
1868 }
1869 } // need new chart
1870
1871 // Arriving here, m_singleChart is opened and OK, or NULL
1872 if (NULL != m_singleChart) {
1873 // Setup the view using the current scale
1874 double set_scale = GetVPScale();
1875
1876 // If the current viewpoint is invalid, set the default scale to
1877 // something reasonable.
1878 if (!GetVP().IsValid())
1879 set_scale = 1. / 20000.;
1880 else { // otherwise, match scale if elected.
1881 double proposed_scale_onscreen;
1882
1883 if (m_bFollow) { // autoset the scale only if in autofollow
1884 double new_scale_ppm =
1885 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1886 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1887 } else
1888 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1889
1890 // This logic will bring a new chart onscreen at roughly twice the true
1891 // paper scale equivalent. Note that first chart opened on application
1892 // startup (bOpenSpecified = true) will open at the config saved scale
1893 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1894 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1895 double equivalent_vp_scale =
1896 GetCanvasScaleFactor() / proposed_scale_onscreen;
1897 double new_scale_ppm =
1898 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1899 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1900 }
1901
1902 if (m_bFollow) { // bounds-check the scale only if in autofollow
1903 proposed_scale_onscreen =
1904 wxMin(proposed_scale_onscreen,
1905 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1906 GetCanvasWidth()));
1907 proposed_scale_onscreen =
1908 wxMax(proposed_scale_onscreen,
1909 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1911 }
1912
1913 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1914 }
1915
1916 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1917 m_singleChart->GetChartSkew() * PI / 180.,
1918 GetVPRotation());
1919 }
1920 } // new stack
1921
1922 else // No change in Chart Stack
1923 {
1924 if ((m_bFollow) && m_singleChart)
1925 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1926 m_singleChart->GetChartSkew() * PI / 180.,
1927 GetVPRotation());
1928 }
1929
1930update_finish:
1931
1932 // TODO
1933 // if( bNewPiano ) UpdateControlBar();
1934
1935 m_bFirstAuto = false; // Auto open on program start
1936
1937 // If we need a Refresh(), do it here...
1938 // But don't duplicate a Refresh() done by SetViewPoint()
1939 if (bNewChart && !bNewView) Refresh(false);
1940
1941#ifdef ocpnUSE_GL
1942 // If a new chart, need to invalidate gl viewport for refresh
1943 // so the fbo gets flushed
1944 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1945#endif
1946
1947 return bNewChart | bNewView;
1948}
1949
1950void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1951 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1952
1953 SetQuiltRefChart(db_index);
1954 if (ChartData) {
1955 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1956 if (pc) {
1957 if (b_autoscale) {
1958 double best_scale_ppm = GetBestVPScale(pc);
1959 SetVPScale(best_scale_ppm);
1960 }
1961 } else
1962 SetQuiltRefChart(-1);
1963 } else
1964 SetQuiltRefChart(-1);
1965}
1966
1967void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1968 std::vector<int> piano_chart_index_array =
1969 GetQuiltExtendedStackdbIndexArray();
1970 int current_db_index = piano_chart_index_array[selected_index];
1971
1972 SelectQuiltRefdbChart(current_db_index);
1973}
1974
1975double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1976 if (pchart) {
1977 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1978
1979 if ((g_bPreserveScaleOnX) ||
1980 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
1981 double new_scale_ppm = GetVPScale();
1982 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1983 } else {
1984 // This logic will bring the new chart onscreen at roughly twice the true
1985 // paper scale equivalent.
1986 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
1987 double equivalent_vp_scale =
1988 GetCanvasScaleFactor() / proposed_scale_onscreen;
1989 double new_scale_ppm =
1990 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1991 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1992 }
1993
1994 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
1995 // set. Otherwise, we get severe performance problems on all platforms
1996
1997 double max_underzoom_multiplier = 2.0;
1998 if (GetVP().b_quilt) {
1999 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2000 pchart->GetChartType(),
2001 pchart->GetChartFamily());
2002 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2003 }
2004
2005 proposed_scale_onscreen = wxMin(
2006 proposed_scale_onscreen,
2007 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2008 max_underzoom_multiplier);
2009
2010 // And, do not allow excessive overzoom either
2011 proposed_scale_onscreen =
2012 wxMax(proposed_scale_onscreen,
2013 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2014
2015 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2016 } else
2017 return 1.0;
2018}
2019
2020void ChartCanvas::SetupCanvasQuiltMode() {
2021 if (GetQuiltMode()) // going to quilt mode
2022 {
2023 ChartData->LockCache();
2024
2025 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2026
2027 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2028
2029 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2030 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2031 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2032 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2033
2034 m_Piano->SetRoundedRectangles(true);
2035
2036 // Select the proper Ref chart
2037 int target_new_dbindex = -1;
2038 if (m_pCurrentStack) {
2039 target_new_dbindex =
2040 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2041
2042 if (-1 != target_new_dbindex) {
2043 if (!IsChartQuiltableRef(target_new_dbindex)) {
2044 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2045 int type = ChartData->GetDBChartType(target_new_dbindex);
2046
2047 // walk the stack up looking for a satisfactory chart
2048 int stack_index = m_pCurrentStack->CurrentStackEntry;
2049
2050 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2051 (stack_index >= 0)) {
2052 int proj_tent = ChartData->GetDBChartProj(
2053 m_pCurrentStack->GetDBIndex(stack_index));
2054 int type_tent = ChartData->GetDBChartType(
2055 m_pCurrentStack->GetDBIndex(stack_index));
2056
2057 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2058 if ((proj == proj_tent) && (type_tent == type)) {
2059 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2060 break;
2061 }
2062 }
2063 stack_index++;
2064 }
2065 }
2066 }
2067 }
2068
2069 if (IsChartQuiltableRef(target_new_dbindex))
2070 SelectQuiltRefdbChart(target_new_dbindex,
2071 false); // Try not to allow a scale change
2072 else
2073 SelectQuiltRefdbChart(-1, false);
2074
2075 m_singleChart = NULL; // Bye....
2076
2077 // Re-qualify the quilt reference chart selection
2078 AdjustQuiltRefChart();
2079
2080 // Restore projection type saved on last quilt mode toggle
2081 // TODO
2082 // if(g_sticky_projection != -1)
2083 // GetVP().SetProjectionType(g_sticky_projection);
2084 // else
2085 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2086 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2087
2088 } else // going to SC Mode
2089 {
2090 std::vector<int> empty_array;
2091 m_Piano->SetActiveKeyArray(empty_array);
2092 m_Piano->SetNoshowIndexArray(empty_array);
2093 m_Piano->SetEclipsedIndexArray(empty_array);
2094
2095 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2096 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2097 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2098 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2099 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2100
2101 m_Piano->SetRoundedRectangles(false);
2102 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2103 }
2104
2105 // When shifting from quilt to single chart mode, select the "best" single
2106 // chart to show
2107 if (!GetQuiltMode()) {
2108 if (ChartData && ChartData->IsValid()) {
2109 UnlockQuilt();
2110
2111 double tLat, tLon;
2112 if (m_bFollow == true) {
2113 tLat = gLat;
2114 tLon = gLon;
2115 } else {
2116 tLat = m_vLat;
2117 tLon = m_vLon;
2118 }
2119
2120 if (!m_singleChart) {
2121 // Build a temporary chart stack based on tLat, tLon
2122 ChartStack TempStack;
2123 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2124 m_groupIndex);
2125
2126 // Iterate over the quilt charts actually shown, looking for the
2127 // largest scale chart that will be in the new chartstack.... This
2128 // will (almost?) always be the reference chart....
2129
2130 ChartBase *Candidate_Chart = NULL;
2131 int cur_max_scale = (int)1e8;
2132
2133 ChartBase *pChart = GetFirstQuiltChart();
2134 while (pChart) {
2135 // Is this pChart in new stack?
2136 int tEntry =
2137 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2138 if (tEntry != -1) {
2139 if (pChart->GetNativeScale() < cur_max_scale) {
2140 Candidate_Chart = pChart;
2141 cur_max_scale = pChart->GetNativeScale();
2142 }
2143 }
2144 pChart = GetNextQuiltChart();
2145 }
2146
2147 m_singleChart = Candidate_Chart;
2148
2149 // If the quilt is empty, there is no "best" chart.
2150 // So, open the smallest scale chart in the current stack
2151 if (NULL == m_singleChart) {
2152 m_singleChart = ChartData->OpenStackChartConditional(
2153 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2154 CHART_FAMILY_DONTCARE);
2155 }
2156 }
2157
2158 // Invalidate all the charts in the quilt,
2159 // as any cached data may be region based and not have fullscreen coverage
2160 InvalidateAllQuiltPatchs();
2161
2162 if (m_singleChart) {
2163 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2164 std::vector<int> one_array;
2165 one_array.push_back(dbi);
2166 m_Piano->SetActiveKeyArray(one_array);
2167 }
2168
2169 if (m_singleChart) {
2170 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2171 }
2172 }
2173 // Invalidate the current stack so that it will be rebuilt on next tick
2174 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2175 }
2176}
2177
2178bool ChartCanvas::IsTempMenuBarEnabled() {
2179#ifdef __WXMSW__
2180 int major;
2181 wxGetOsVersion(&major);
2182 return (major >
2183 5); // For Windows, function is only available on Vista and above
2184#else
2185 return true;
2186#endif
2187}
2188
2189double ChartCanvas::GetCanvasRangeMeters() {
2190 int width, height;
2191 GetSize(&width, &height);
2192 int minDimension = wxMin(width, height);
2193
2194 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2195 range *= cos(GetVP().clat * PI / 180.);
2196 return range;
2197}
2198
2199void ChartCanvas::SetCanvasRangeMeters(double range) {
2200 int width, height;
2201 GetSize(&width, &height);
2202 int minDimension = wxMin(width, height);
2203
2204 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2205 SetVPScale(scale_ppm / 2);
2206}
2207
2208bool ChartCanvas::SetUserOwnship() {
2209 // Look for user defined ownship image
2210 // This may be found in the shared data location along with other user
2211 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2212 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2213 double factor_dusk = 0.5;
2214 double factor_night = 0.25;
2215
2216 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2217 m_pos_image_user_day = new wxImage;
2218 *m_pos_image_user_day = pbmp->ConvertToImage();
2219 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2220
2221 int gimg_width = m_pos_image_user_day->GetWidth();
2222 int gimg_height = m_pos_image_user_day->GetHeight();
2223
2224 // Make dusk and night images
2225 m_pos_image_user_dusk = new wxImage;
2226 m_pos_image_user_night = new wxImage;
2227
2228 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2229 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2230
2231 for (int iy = 0; iy < gimg_height; iy++) {
2232 for (int ix = 0; ix < gimg_width; ix++) {
2233 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2234 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2235 m_pos_image_user_day->GetGreen(ix, iy),
2236 m_pos_image_user_day->GetBlue(ix, iy));
2237 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2238 hsv.value = hsv.value * factor_dusk;
2239 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2240 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2241 nrgb.blue);
2242
2243 hsv = wxImage::RGBtoHSV(rgb);
2244 hsv.value = hsv.value * factor_night;
2245 nrgb = wxImage::HSVtoRGB(hsv);
2246 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2247 nrgb.blue);
2248 }
2249 }
2250 }
2251
2252 // Make some alternate greyed out day/dusk/night images
2253 m_pos_image_user_grey_day = new wxImage;
2254 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2255
2256 m_pos_image_user_grey_dusk = new wxImage;
2257 m_pos_image_user_grey_night = new wxImage;
2258
2259 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2260 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2261
2262 for (int iy = 0; iy < gimg_height; iy++) {
2263 for (int ix = 0; ix < gimg_width; ix++) {
2264 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2265 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2266 m_pos_image_user_grey_day->GetGreen(ix, iy),
2267 m_pos_image_user_grey_day->GetBlue(ix, iy));
2268 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2269 hsv.value = hsv.value * factor_dusk;
2270 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2271 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2272 nrgb.blue);
2273
2274 hsv = wxImage::RGBtoHSV(rgb);
2275 hsv.value = hsv.value * factor_night;
2276 nrgb = wxImage::HSVtoRGB(hsv);
2277 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2278 nrgb.blue);
2279 }
2280 }
2281 }
2282
2283 // Make a yellow image for rendering under low accuracy chart conditions
2284 m_pos_image_user_yellow_day = new wxImage;
2285 m_pos_image_user_yellow_dusk = new wxImage;
2286 m_pos_image_user_yellow_night = new wxImage;
2287
2288 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2289 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2290 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2291
2292 for (int iy = 0; iy < gimg_height; iy++) {
2293 for (int ix = 0; ix < gimg_width; ix++) {
2294 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2295 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2296 m_pos_image_user_grey_day->GetGreen(ix, iy),
2297 m_pos_image_user_grey_day->GetBlue(ix, iy));
2298
2299 // Simply remove all "blue" from the greyscaled image...
2300 // so, what is not black becomes yellow.
2301 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2302 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2303 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2304
2305 hsv = wxImage::RGBtoHSV(rgb);
2306 hsv.value = hsv.value * factor_dusk;
2307 nrgb = wxImage::HSVtoRGB(hsv);
2308 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2309
2310 hsv = wxImage::RGBtoHSV(rgb);
2311 hsv.value = hsv.value * factor_night;
2312 nrgb = wxImage::HSVtoRGB(hsv);
2313 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2314 0);
2315 }
2316 }
2317 }
2318
2319 return true;
2320 } else
2321 return false;
2322}
2323
2325 m_display_size_mm = size;
2326
2327 // int sx, sy;
2328 // wxDisplaySize( &sx, &sy );
2329
2330 // Calculate logical pixels per mm for later reference.
2331 wxSize sd = g_Platform->getDisplaySize();
2332 double horizontal = sd.x;
2333 // Set DPI (Win) scale factor
2334 g_scaler = g_Platform->GetDisplayDIPMult(this);
2335
2336 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2337 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2338
2339 if (ps52plib) {
2340 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2341 ps52plib->SetPPMM(m_pix_per_mm);
2342 }
2343
2344 wxString msg;
2345 msg.Printf(
2346 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2347 "%d:%d ",
2348 m_display_size_mm, sd.x, sd.y);
2349 wxLogMessage(msg);
2350
2351 int ssx, ssy;
2352 ssx = g_monitor_info[g_current_monitor].width;
2353 ssy = g_monitor_info[g_current_monitor].height;
2354 msg.Printf("monitor size: %d %d", ssx, ssy);
2355 wxLogMessage(msg);
2356
2357 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2358}
2359#if 0
2360void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2361{
2362 wxString msg(event.m_string.c_str(), wxConvUTF8);
2363 // if cpus are removed between runs
2364 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2365 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2366 }
2367
2368 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2369 {
2370 compress_msg_array.RemoveAt(event.thread);
2371 compress_msg_array.Insert( msg, event.thread);
2372 }
2373 else
2374 compress_msg_array.Add(msg);
2375
2376
2377 wxString combined_msg;
2378 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2379 combined_msg += compress_msg_array[i];
2380 combined_msg += "\n";
2381 }
2382
2383 bool skip = false;
2384 pprog->Update(pprog_count, combined_msg, &skip );
2385 pprog->SetSize(pprog_size);
2386 if(skip)
2387 b_skipout = skip;
2388}
2389#endif
2390void ChartCanvas::InvalidateGL() {
2391 if (!m_glcc) return;
2392#ifdef ocpnUSE_GL
2393 if (g_bopengl) m_glcc->Invalidate();
2394#endif
2395 if (m_Compass) m_Compass->UpdateStatus(true);
2396}
2397
2398int ChartCanvas::GetCanvasChartNativeScale() {
2399 int ret = 1;
2400 if (!VPoint.b_quilt) {
2401 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2402 } else
2403 ret = (int)m_pQuilt->GetRefNativeScale();
2404
2405 return ret;
2406}
2407
2408ChartBase *ChartCanvas::GetChartAtCursor() {
2409 ChartBase *target_chart;
2410 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2411 target_chart = m_singleChart;
2412 else if (VPoint.b_quilt)
2413 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2414 else
2415 target_chart = NULL;
2416 return target_chart;
2417}
2418
2419ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2420 ChartBase *target_chart;
2421 if (VPoint.b_quilt)
2422 target_chart =
2423 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2424 else
2425 target_chart = NULL;
2426 return target_chart;
2427}
2428
2429int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2430 int new_dbIndex = -1;
2431 if (!VPoint.b_quilt) {
2432 if (m_pCurrentStack) {
2433 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2434 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2435 if (sc >= scale) {
2436 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2437 break;
2438 }
2439 }
2440 }
2441 } else {
2442 // Using the current quilt, select a useable reference chart
2443 // Said chart will be in the extended (possibly full-screen) stack,
2444 // And will have a scale equal to or just greater than the stipulated
2445 // value
2446 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2447 if (im > 0) {
2448 for (unsigned int is = 0; is < im; is++) {
2449 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2450 m_pQuilt->GetExtendedStackIndexArray()[is]);
2451 if ((m.Scale_ge(
2452 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2453 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2454 break;
2455 }
2456 }
2457 }
2458 }
2459
2460 return new_dbIndex;
2461}
2462
2463void ChartCanvas::EnablePaint(bool b_enable) {
2464 m_b_paint_enable = b_enable;
2465#ifdef ocpnUSE_GL
2466 if (m_glcc) m_glcc->EnablePaint(b_enable);
2467#endif
2468}
2469
2470bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2471
2472void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2473
2474std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2475 return m_pQuilt->GetQuiltIndexArray();
2476 ;
2477}
2478
2479void ChartCanvas::SetQuiltMode(bool b_quilt) {
2480 VPoint.b_quilt = b_quilt;
2481 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2482}
2483
2484bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2485
2486int ChartCanvas::GetQuiltReferenceChartIndex() {
2487 return m_pQuilt->GetRefChartdbIndex();
2488}
2489
2490void ChartCanvas::InvalidateAllQuiltPatchs() {
2491 m_pQuilt->InvalidateAllQuiltPatchs();
2492}
2493
2494ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2495 return m_pQuilt->GetLargestScaleChart();
2496}
2497
2498ChartBase *ChartCanvas::GetFirstQuiltChart() {
2499 return m_pQuilt->GetFirstChart();
2500}
2501
2502ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2503
2504int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2505
2506void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2507 m_pQuilt->SetHiliteIndex(dbIndex);
2508}
2509
2510void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2511 m_pQuilt->SetHiliteIndexArray(hilite_array);
2512}
2513
2514void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2515 m_pQuilt->ClearHiliteIndexArray();
2516}
2517
2518std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2519 bool flag2) {
2520 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2521}
2522
2523int ChartCanvas::GetQuiltRefChartdbIndex() {
2524 return m_pQuilt->GetRefChartdbIndex();
2525}
2526
2527std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2528 return m_pQuilt->GetExtendedStackIndexArray();
2529}
2530
2531std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2532 return m_pQuilt->GetFullscreenIndexArray();
2533}
2534
2535std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2536 return m_pQuilt->GetEclipsedStackIndexArray();
2537}
2538
2539void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2540
2541double ChartCanvas::GetQuiltMaxErrorFactor() {
2542 return m_pQuilt->GetMaxErrorFactor();
2543}
2544
2545bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2546 return m_pQuilt->IsChartQuiltableRef(db_index);
2547}
2548
2549bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2550 double chartMaxScale =
2551 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2552 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2553}
2554
2555void ChartCanvas::StartMeasureRoute() {
2556 if (!m_routeState) { // no measure tool if currently creating route
2557 if (m_bMeasure_Active) {
2558 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2559 m_pMeasureRoute = NULL;
2560 }
2561
2562 m_bMeasure_Active = true;
2563 m_nMeasureState = 1;
2564 m_bDrawingRoute = false;
2565
2566 SetCursor(*pCursorPencil);
2567 Refresh();
2568 }
2569}
2570
2571void ChartCanvas::CancelMeasureRoute() {
2572 m_bMeasure_Active = false;
2573 m_nMeasureState = 0;
2574 m_bDrawingRoute = false;
2575
2576 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2577 m_pMeasureRoute = NULL;
2578
2579 SetCursor(*pCursorArrow);
2580}
2581
2582ViewPort &ChartCanvas::GetVP() { return VPoint; }
2583
2584void ChartCanvas::SetVP(ViewPort &vp) {
2585 VPoint = vp;
2586 VPoint.SetPixelScale(m_displayScale);
2587}
2588
2589// void ChartCanvas::SetFocus()
2590// {
2591// printf("set %d\n", m_canvasIndex);
2592// //wxWindow:SetFocus();
2593// }
2594
2595void ChartCanvas::TriggerDeferredFocus() {
2596 // #if defined(__WXGTK__) || defined(__WXOSX__)
2597
2598 m_deferredFocusTimer.Start(20, true);
2599
2600#if defined(__WXGTK__) || defined(__WXOSX__)
2601 gFrame->Raise();
2602#endif
2603
2604 // gFrame->Raise();
2605 // #else
2606 // SetFocus();
2607 // Refresh(true);
2608 // #endif
2609}
2610
2611void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2612 SetFocus();
2613 Refresh(true);
2614}
2615
2616void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2617 if (SendKeyEventToPlugins(event))
2618 return; // PlugIn did something, and does not want the canvas to do
2619 // anything else
2620
2621 int key_char = event.GetKeyCode();
2622 switch (key_char) {
2623 case '?':
2624 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2625 break;
2626 case '+':
2627 ZoomCanvas(g_plus_minus_zoom_factor, false);
2628 break;
2629 case '-':
2630 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2631 break;
2632 default:
2633 break;
2634 }
2635 if (g_benable_rotate) {
2636 switch (key_char) {
2637 case ']':
2638 RotateCanvas(1);
2639 Refresh();
2640 break;
2641
2642 case '[':
2643 RotateCanvas(-1);
2644 Refresh();
2645 break;
2646
2647 case '\\':
2648 DoRotateCanvas(0);
2649 break;
2650 }
2651 }
2652
2653 event.Skip();
2654}
2655
2656void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2657 if (SendKeyEventToPlugins(event))
2658 return; // PlugIn did something, and does not want the canvas to do
2659 // anything else
2660
2661 bool b_handled = false;
2662
2663 m_modkeys = event.GetModifiers();
2664
2665 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2666
2667#ifdef OCPN_ALT_MENUBAR
2668#ifndef __WXOSX__
2669 // If the permanent menubar is disabled, we show it temporarily when Alt is
2670 // pressed or when Alt + a letter is presssed (for the top-menu-level
2671 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2672 // some special cases.
2673 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2674 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2675 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2676 if (!g_bTempShowMenuBar) {
2677 g_bTempShowMenuBar = true;
2678 parent_frame->ApplyGlobalSettings(false);
2679 }
2680 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2681 event.Skip();
2682 return;
2683 }
2684 // If another key is pressed while Alt is down, do NOT toggle the menus when
2685 // Alt is released
2686 if (event.GetKeyCode() != WXK_ALT) {
2687 m_bMayToggleMenuBar = false;
2688 }
2689 }
2690#endif
2691#endif
2692
2693 // HOTKEYS
2694 switch (event.GetKeyCode()) {
2695 case WXK_TAB:
2696 // parent_frame->SwitchKBFocus( this );
2697 break;
2698
2699 case WXK_MENU:
2700 int x, y;
2701 event.GetPosition(&x, &y);
2702 m_FinishRouteOnKillFocus = false;
2703 CallPopupMenu(x, y);
2704 m_FinishRouteOnKillFocus = true;
2705 break;
2706
2707 case WXK_ALT:
2708 m_modkeys |= wxMOD_ALT;
2709 break;
2710
2711 case WXK_CONTROL:
2712 m_modkeys |= wxMOD_CONTROL;
2713 break;
2714
2715#ifdef __WXOSX__
2716 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2717 case WXK_RAW_CONTROL:
2718 m_modkeys |= wxMOD_RAW_CONTROL;
2719 break;
2720#endif
2721
2722 case WXK_LEFT:
2723 if (m_modkeys == wxMOD_CONTROL)
2724 parent_frame->DoStackDown(this);
2725 else if (g_bsmoothpanzoom) {
2726 StartTimedMovement();
2727 m_panx = -1;
2728 } else {
2729 PanCanvas(-panspeed, 0);
2730 }
2731 b_handled = true;
2732 break;
2733
2734 case WXK_UP:
2735 if (g_bsmoothpanzoom) {
2736 StartTimedMovement();
2737 m_pany = -1;
2738 } else
2739 PanCanvas(0, -panspeed);
2740 b_handled = true;
2741 break;
2742
2743 case WXK_RIGHT:
2744 if (m_modkeys == wxMOD_CONTROL)
2745 parent_frame->DoStackUp(this);
2746 else if (g_bsmoothpanzoom) {
2747 StartTimedMovement();
2748 m_panx = 1;
2749 } else
2750 PanCanvas(panspeed, 0);
2751 b_handled = true;
2752
2753 break;
2754
2755 case WXK_DOWN:
2756 if (g_bsmoothpanzoom) {
2757 StartTimedMovement();
2758 m_pany = 1;
2759 } else
2760 PanCanvas(0, panspeed);
2761 b_handled = true;
2762 break;
2763
2764 case WXK_F2:
2765 TogglebFollow();
2766 break;
2767
2768 case WXK_F3: {
2769 SetShowENCText(!GetShowENCText());
2770 Refresh(true);
2771 InvalidateGL();
2772 break;
2773 }
2774 case WXK_F4:
2775 if (!m_bMeasure_Active) {
2776 if (event.ShiftDown())
2777 m_bMeasure_DistCircle = true;
2778 else
2779 m_bMeasure_DistCircle = false;
2780
2781 StartMeasureRoute();
2782 } else {
2783 CancelMeasureRoute();
2784
2785 SetCursor(*pCursorArrow);
2786
2787 // SurfaceToolbar();
2788 InvalidateGL();
2789 Refresh(false);
2790 }
2791
2792 break;
2793
2794 case WXK_F5:
2795 parent_frame->ToggleColorScheme();
2796 gFrame->Raise();
2797 TriggerDeferredFocus();
2798 break;
2799
2800 case WXK_F6: {
2801 int mod = m_modkeys & wxMOD_SHIFT;
2802 if (mod != m_brightmod) {
2803 m_brightmod = mod;
2804 m_bbrightdir = !m_bbrightdir;
2805 }
2806
2807 if (!m_bbrightdir) {
2808 g_nbrightness -= 10;
2809 if (g_nbrightness <= MIN_BRIGHT) {
2810 g_nbrightness = MIN_BRIGHT;
2811 m_bbrightdir = true;
2812 }
2813 } else {
2814 g_nbrightness += 10;
2815 if (g_nbrightness >= MAX_BRIGHT) {
2816 g_nbrightness = MAX_BRIGHT;
2817 m_bbrightdir = false;
2818 }
2819 }
2820
2821 SetScreenBrightness(g_nbrightness);
2822 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2823
2824 SetFocus(); // just in case the external program steals it....
2825 gFrame->Raise(); // And reactivate the application main
2826
2827 break;
2828 }
2829
2830 case WXK_F7:
2831 parent_frame->DoStackDown(this);
2832 break;
2833
2834 case WXK_F8:
2835 parent_frame->DoStackUp(this);
2836 break;
2837
2838#ifndef __WXOSX__
2839 case WXK_F9: {
2840 ToggleCanvasQuiltMode();
2841 break;
2842 }
2843#endif
2844
2845 case WXK_F11:
2846 parent_frame->ToggleFullScreen();
2847 b_handled = true;
2848 break;
2849
2850 case WXK_F12: {
2851 if (m_modkeys == wxMOD_ALT) {
2852 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2853 } else {
2854 ToggleChartOutlines();
2855 }
2856 break;
2857 }
2858
2859 case WXK_PAUSE: // Drop MOB
2860 parent_frame->ActivateMOB();
2861 break;
2862
2863 // NUMERIC PAD
2864 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2865 case WXK_PAGEUP: {
2866 ZoomCanvas(g_plus_minus_zoom_factor, false);
2867 break;
2868 }
2869 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2870 case WXK_PAGEDOWN: {
2871 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2872 break;
2873 }
2874 case WXK_DELETE:
2875 case WXK_BACK:
2876 if (m_bMeasure_Active) {
2877 if (m_nMeasureState > 2) {
2878 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2879 m_pMeasureRoute->m_lastMousePointIndex =
2880 m_pMeasureRoute->GetnPoints();
2881 m_nMeasureState--;
2882 gFrame->RefreshAllCanvas();
2883 } else {
2884 CancelMeasureRoute();
2885 StartMeasureRoute();
2886 }
2887 }
2888 break;
2889 default:
2890 break;
2891 }
2892
2893 if (event.GetKeyCode() < 128) // ascii
2894 {
2895 int key_char = event.GetKeyCode();
2896
2897 // Handle both QWERTY and AZERTY keyboard separately for a few control
2898 // codes
2899 if (!g_b_assume_azerty) {
2900#ifdef __WXMAC__
2901 if (g_benable_rotate) {
2902 switch (key_char) {
2903 // On other platforms these are handled in OnKeyChar, which
2904 // (apparently) works better in some locales. On OS X it is better
2905 // to handle them here, since pressing Alt (which should change the
2906 // rotation speed) changes the key char and so prevents the keys
2907 // from working.
2908 case ']':
2909 RotateCanvas(1);
2910 b_handled = true;
2911 break;
2912
2913 case '[':
2914 RotateCanvas(-1);
2915 b_handled = true;
2916 break;
2917
2918 case '\\':
2919 DoRotateCanvas(0);
2920 b_handled = true;
2921 break;
2922 }
2923 }
2924#endif
2925 } else { // AZERTY
2926 switch (key_char) {
2927 case 43:
2928 ZoomCanvas(g_plus_minus_zoom_factor, false);
2929 break;
2930
2931 case 54: // '-' alpha/num pad
2932 // case 56: // '_' alpha/num pad
2933 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2934 break;
2935 }
2936 }
2937
2938#ifdef __WXOSX__
2939 // Ctrl+Cmd+F toggles fullscreen on macOS
2940 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
2941 m_modkeys & wxMOD_RAW_CONTROL) {
2942 parent_frame->ToggleFullScreen();
2943 return;
2944 }
2945#endif
2946
2947 if (event.ControlDown()) key_char -= 64;
2948
2949 if (key_char >= '0' && key_char <= '9')
2950 SetGroupIndex(key_char - '0');
2951 else
2952
2953 switch (key_char) {
2954 case 'A':
2955 SetShowENCAnchor(!GetShowENCAnchor());
2956 ReloadVP();
2957
2958 break;
2959
2960 case 'C':
2961 parent_frame->ToggleColorScheme();
2962 break;
2963
2964 case 'D': {
2965 int x, y;
2966 event.GetPosition(&x, &y);
2967 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
2968 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
2969 // First find out what kind of chart is being used
2970 if (!pPopupDetailSlider) {
2971 if (VPoint.b_quilt) {
2972 if (m_pQuilt) {
2973 if (m_pQuilt->GetChartAtPix(
2974 VPoint,
2975 wxPoint(
2976 x, y))) // = null if no chart loaded for this point
2977 {
2978 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2979 ->GetChartType();
2980 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2981 ->GetChartFamily();
2982 }
2983 }
2984 } else {
2985 if (m_singleChart) {
2986 ChartType = m_singleChart->GetChartType();
2987 ChartFam = m_singleChart->GetChartFamily();
2988 }
2989 }
2990 // If a charttype is found show the popupslider
2991 if ((ChartType != CHART_TYPE_UNKNOWN) ||
2992 (ChartFam != CHART_FAMILY_UNKNOWN)) {
2994 this, -1, ChartType, ChartFam,
2995 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
2996 wxDefaultSize, wxSIMPLE_BORDER, "");
2998 }
2999 } else //( !pPopupDetailSlider ) close popupslider
3000 {
3002 pPopupDetailSlider = NULL;
3003 }
3004 break;
3005 }
3006
3007 case 'E':
3008 m_nmea_log->Show();
3009 m_nmea_log->Raise();
3010 break;
3011
3012 case 'L':
3013 SetShowENCLights(!GetShowENCLights());
3014 ReloadVP();
3015
3016 break;
3017
3018 case 'M':
3019 if (event.ShiftDown())
3020 m_bMeasure_DistCircle = true;
3021 else
3022 m_bMeasure_DistCircle = false;
3023
3024 StartMeasureRoute();
3025 break;
3026
3027 case 'N':
3028 if (g_bInlandEcdis && ps52plib) {
3029 SetENCDisplayCategory((_DisCat)STANDARD);
3030 }
3031 break;
3032
3033 case 'O':
3034 ToggleChartOutlines();
3035 break;
3036
3037 case 'Q':
3038 ToggleCanvasQuiltMode();
3039 break;
3040
3041 case 'P':
3042 parent_frame->ToggleTestPause();
3043 break;
3044 case 'R':
3045 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3046 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3047 g_iNavAidRadarRingsNumberVisible = 1;
3048 else if (!g_bNavAidRadarRingsShown &&
3049 g_iNavAidRadarRingsNumberVisible == 1)
3050 g_iNavAidRadarRingsNumberVisible = 0;
3051 break;
3052 case 'S':
3053 SetShowENCDepth(!m_encShowDepth);
3054 ReloadVP();
3055 break;
3056
3057 case 'T':
3058 SetShowENCText(!GetShowENCText());
3059 ReloadVP();
3060 break;
3061
3062 case 'U':
3063 SetShowENCDataQual(!GetShowENCDataQual());
3064 ReloadVP();
3065 break;
3066
3067 case 'V':
3068 m_bShowNavobjects = !m_bShowNavobjects;
3069 Refresh(true);
3070 break;
3071
3072 case 'W': // W Toggle CPA alarm
3073 ToggleCPAWarn();
3074
3075 break;
3076
3077 case 1: // Ctrl A
3078 TogglebFollow();
3079
3080 break;
3081
3082 case 2: // Ctrl B
3083 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3084 break;
3085
3086 case 13: // Ctrl M // Drop Marker at cursor
3087 {
3088 if (event.ControlDown()) gFrame->DropMarker(false);
3089 break;
3090 }
3091
3092 case 14: // Ctrl N - Activate next waypoint in a route
3093 {
3094 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3095 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3096 if ((indexActive + 1) <= r->GetnPoints()) {
3098 InvalidateGL();
3099 Refresh(false);
3100 }
3101 }
3102 break;
3103 }
3104
3105 case 15: // Ctrl O - Drop Marker at boat's position
3106 {
3107 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3108 break;
3109 }
3110
3111 case 32: // Special needs use space bar
3112 {
3113 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3114 break;
3115 }
3116
3117 case -32: // Ctrl Space // Drop MOB
3118 {
3119 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3120
3121 break;
3122 }
3123
3124 case -20: // Ctrl ,
3125 {
3126 parent_frame->DoSettings();
3127 break;
3128 }
3129 case 17: // Ctrl Q
3130 parent_frame->Close();
3131 return;
3132
3133 case 18: // Ctrl R
3134 StartRoute();
3135 return;
3136
3137 case 20: // Ctrl T
3138 if (NULL == pGoToPositionDialog) // There is one global instance of
3139 // the Go To Position Dialog
3141 pGoToPositionDialog->SetCanvas(this);
3142 pGoToPositionDialog->Show();
3143 break;
3144
3145 case 25: // Ctrl Y
3146 if (undo->AnythingToRedo()) {
3147 undo->RedoNextAction();
3148 InvalidateGL();
3149 Refresh(false);
3150 }
3151 break;
3152
3153 case 26:
3154 if (event.ShiftDown()) { // Shift-Ctrl-Z
3155 if (undo->AnythingToRedo()) {
3156 undo->RedoNextAction();
3157 InvalidateGL();
3158 Refresh(false);
3159 }
3160 } else { // Ctrl Z
3161 if (undo->AnythingToUndo()) {
3162 undo->UndoLastAction();
3163 InvalidateGL();
3164 Refresh(false);
3165 }
3166 }
3167 break;
3168
3169 case 27:
3170 // Generic break
3171 if (m_bMeasure_Active) {
3172 CancelMeasureRoute();
3173
3174 SetCursor(*pCursorArrow);
3175
3176 // SurfaceToolbar();
3177 gFrame->RefreshAllCanvas();
3178 }
3179
3180 if (m_routeState) // creating route?
3181 {
3182 FinishRoute();
3183 // SurfaceToolbar();
3184 InvalidateGL();
3185 Refresh(false);
3186 }
3187
3188 break;
3189
3190 case 7: // Ctrl G
3191 switch (gamma_state) {
3192 case (0):
3193 r_gamma_mult = 0;
3194 g_gamma_mult = 1;
3195 b_gamma_mult = 0;
3196 gamma_state = 1;
3197 break;
3198 case (1):
3199 r_gamma_mult = 1;
3200 g_gamma_mult = 0;
3201 b_gamma_mult = 0;
3202 gamma_state = 2;
3203 break;
3204 case (2):
3205 r_gamma_mult = 1;
3206 g_gamma_mult = 1;
3207 b_gamma_mult = 1;
3208 gamma_state = 0;
3209 break;
3210 }
3211 SetScreenBrightness(g_nbrightness);
3212
3213 break;
3214
3215 case 9: // Ctrl I
3216 if (event.ControlDown()) {
3217 m_bShowCompassWin = !m_bShowCompassWin;
3218 SetShowGPSCompassWindow(m_bShowCompassWin);
3219 Refresh(false);
3220 }
3221 break;
3222
3223 default:
3224 break;
3225
3226 } // switch
3227 }
3228
3229 // Allow OnKeyChar to catch the key events too.
3230 if (!b_handled) {
3231 event.Skip();
3232 }
3233}
3234
3235void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3236 if (SendKeyEventToPlugins(event))
3237 return; // PlugIn did something, and does not want the canvas to do
3238 // anything else
3239
3240 switch (event.GetKeyCode()) {
3241 case WXK_TAB:
3242 parent_frame->SwitchKBFocus(this);
3243 break;
3244
3245 case WXK_LEFT:
3246 case WXK_RIGHT:
3247 m_panx = 0;
3248 if (!m_pany) m_panspeed = 0;
3249 break;
3250
3251 case WXK_UP:
3252 case WXK_DOWN:
3253 m_pany = 0;
3254 if (!m_panx) m_panspeed = 0;
3255 break;
3256
3257 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3258 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3259 case WXK_PAGEUP:
3260 case WXK_PAGEDOWN:
3261 if (m_mustmove) DoMovement(m_mustmove);
3262
3263 m_zoom_factor = 1;
3264 break;
3265
3266 case WXK_ALT:
3267 m_modkeys &= ~wxMOD_ALT;
3268#ifdef OCPN_ALT_MENUBAR
3269#ifndef __WXOSX__
3270 // If the permanent menu bar is disabled, and we are not in the middle of
3271 // another key combo, then show the menu bar temporarily when Alt is
3272 // released (or hide it if already visible).
3273 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3274 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3275 parent_frame->ApplyGlobalSettings(false);
3276 }
3277 m_bMayToggleMenuBar = true;
3278#endif
3279#endif
3280 break;
3281
3282 case WXK_CONTROL:
3283 m_modkeys &= ~wxMOD_CONTROL;
3284 break;
3285 }
3286
3287 if (event.GetKeyCode() < 128) // ascii
3288 {
3289 int key_char = event.GetKeyCode();
3290
3291 // Handle both QWERTY and AZERTY keyboard separately for a few control
3292 // codes
3293 if (!g_b_assume_azerty) {
3294 switch (key_char) {
3295 case '+':
3296 case '=':
3297 case '-':
3298 case '_':
3299 case 54:
3300 case 56: // '_' alpha/num pad
3301 DoMovement(m_mustmove);
3302
3303 // m_zoom_factor = 1;
3304 break;
3305 case '[':
3306 case ']':
3307 DoMovement(m_mustmove);
3308 m_rotation_speed = 0;
3309 break;
3310 }
3311 } else {
3312 switch (key_char) {
3313 case 43:
3314 case 54: // '-' alpha/num pad
3315 case 56: // '_' alpha/num pad
3316 DoMovement(m_mustmove);
3317
3318 m_zoom_factor = 1;
3319 break;
3320 }
3321 }
3322 }
3323 event.Skip();
3324}
3325
3326void ChartCanvas::ToggleChartOutlines() {
3327 m_bShowOutlines = !m_bShowOutlines;
3328
3329 Refresh(false);
3330
3331#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3332 // needs a full refresh
3333 if (g_bopengl) InvalidateGL();
3334#endif
3335}
3336
3337void ChartCanvas::ToggleLookahead() {
3338 m_bLookAhead = !m_bLookAhead;
3339 m_OSoffsetx = 0; // center ownship
3340 m_OSoffsety = 0;
3341}
3342
3343void ChartCanvas::SetUpMode(int mode) {
3344 m_upMode = mode;
3345
3346 if (mode != NORTH_UP_MODE) {
3347 // Stuff the COGAvg table in case COGUp is selected
3348 double stuff = 0;
3349 if (!std::isnan(gCog)) stuff = gCog;
3350
3351 if (g_COGAvgSec > 0) {
3352 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3353 }
3354 g_COGAvg = stuff;
3355 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3356 } else {
3357 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3358 SetVPRotation(GetVPSkew());
3359 else
3360 SetVPRotation(0); /* reset to north up */
3361 }
3362
3363 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3364 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3365
3366 UpdateGPSCompassStatusBox(true);
3367 gFrame->DoChartUpdate();
3368}
3369
3370bool ChartCanvas::DoCanvasCOGSet() {
3371 if (GetUpMode() == NORTH_UP_MODE) return false;
3372 double cog_use = g_COGAvg;
3373 if (g_btenhertz) cog_use = gCog;
3374
3375 double rotation = 0;
3376 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3377 rotation = -gHdt * PI / 180.;
3378 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3379 rotation = -cog_use * PI / 180.;
3380
3381 SetVPRotation(rotation);
3382 return true;
3383}
3384
3385double easeOutCubic(double t) {
3386 // Starts quickly and slows down toward the end
3387 return 1.0 - pow(1.0 - t, 3.0);
3388}
3389
3390void ChartCanvas::StartChartDragInertia() {
3391 m_bChartDragging = false;
3392
3393 // Set some parameters
3394 m_chart_drag_inertia_time = 750; // msec
3395 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3396 m_last_elapsed = 0;
3397
3398 // Calculate ending drag velocity
3399 size_t n_vel = 10;
3400 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3401 int xacc = 0;
3402 int yacc = 0;
3403 double tacc = 0;
3404 size_t length = m_drag_vec_t.size();
3405 for (size_t i = 0; i < n_vel; i++) {
3406 xacc += m_drag_vec_x.at(length - 1 - i);
3407 yacc += m_drag_vec_y.at(length - 1 - i);
3408 tacc += m_drag_vec_t.at(length - 1 - i);
3409 }
3410
3411 if (tacc == 0) return;
3412
3413 m_chart_drag_velocity_x = xacc / tacc;
3414 m_chart_drag_velocity_y = yacc / tacc;
3415
3416 m_chart_drag_inertia_active = true;
3417 // First callback as fast as possible.
3418 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3419}
3420
3421void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3422 if (!m_chart_drag_inertia_active) return;
3423 // Calculate time fraction from 0..1
3424 wxLongLong now = wxGetLocalTimeMillis();
3425 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3426 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3427 if (t > 1.0) t = 1.0;
3428 double e = 1.0 - easeOutCubic(t); // 0..1
3429
3430 double dx =
3431 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3432 double dy =
3433 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3434
3435 m_last_elapsed = elapsed;
3436
3437 // Ensure that target destination lies on whole-pixel boundary
3438 // This allows the render engine to use a faster FBO copy method for drawing
3439 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3440 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3441 double inertia_lat, inertia_lon;
3442 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3443 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3444 // Check if ownship has moved off-screen
3445 if (!IsOwnshipOnScreen()) {
3446 m_bFollow = false; // update the follow flag
3447 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3448 UpdateFollowButtonState();
3449 m_OSoffsetx = 0;
3450 m_OSoffsety = 0;
3451 } else {
3452 m_OSoffsetx += dx;
3453 m_OSoffsety -= dy;
3454 }
3455
3456 Refresh(false);
3457
3458 // Stop condition
3459 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3460 m_chart_drag_inertia_timer.Stop();
3461
3462 // Disable chart pan movement logic
3463 m_target_lat = GetVP().clat;
3464 m_target_lon = GetVP().clon;
3465 m_pan_drag.x = m_pan_drag.y = 0;
3466 m_panx = m_pany = 0;
3467 m_chart_drag_inertia_active = false;
3468 DoCanvasUpdate();
3469
3470 } else {
3471 int target_redraw_interval = 40; // msec
3472 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3473 }
3474}
3475
3476void ChartCanvas::StopMovement() {
3477 m_panx = m_pany = 0;
3478 m_panspeed = 0;
3479 m_zoom_factor = 1;
3480 m_rotation_speed = 0;
3481 m_mustmove = 0;
3482#if 0
3483#if !defined(__WXGTK__) && !defined(__WXQT__)
3484 SetFocus();
3485 gFrame->Raise();
3486#endif
3487#endif
3488}
3489
3490/* instead of integrating in timer callbacks
3491 (which do not always get called fast enough)
3492 we can perform the integration of movement
3493 at each render frame based on the time change */
3494bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3495 // Start/restart the stop movement timer
3496 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3497
3498 if (!pMovementTimer->IsRunning()) {
3499 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3500 }
3501
3502 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3503 // already moving, gets called again because of key-repeat event
3504 return false;
3505 }
3506
3507 m_last_movement_time = wxDateTime::UNow();
3508
3509 return true;
3510}
3511void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3512 int nstep) {
3513 // Save the target
3514 m_target_lat = target_lat;
3515 m_target_lon = target_lon;
3516
3517 // Save the start point
3518 m_start_lat = GetVP().clat;
3519 m_start_lon = GetVP().clon;
3520
3521 m_VPMovementTimer.Start(1, true); // oneshot
3522 m_timed_move_vp_active = true;
3523 m_stvpc = 0;
3524 m_timedVP_step = nstep;
3525}
3526
3527void ChartCanvas::DoTimedMovementVP() {
3528 if (!m_timed_move_vp_active) return; // not active
3529 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3530 StopMovement();
3531 return;
3532 }
3533 // Stop condition
3534 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3535 double d2 =
3536 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3537 d2 = pow(d2, 0.5);
3538
3539 if (d2 < one_pix) {
3540 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3541 StopMovementVP();
3542 return;
3543 }
3544
3545 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3546 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3547 // StopMovementVP();
3548 // return;
3549 // }
3550
3551 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3552 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3553
3554 m_run_lat = new_lat;
3555 m_run_lon = new_lon;
3556
3557 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3558}
3559
3560void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3561
3562void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3563
3564void ChartCanvas::StartTimedMovementTarget() {}
3565
3566void ChartCanvas::DoTimedMovementTarget() {}
3567
3568void ChartCanvas::StopMovementTarget() {}
3569int ntm;
3570
3571void ChartCanvas::DoTimedMovement() {
3572 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3573 !m_rotation_speed)
3574 return; /* not moving */
3575
3576 wxDateTime now = wxDateTime::UNow();
3577 long dt = 0;
3578 if (m_last_movement_time.IsValid())
3579 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3580
3581 m_last_movement_time = now;
3582
3583 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3584 dt = 500;
3585
3586 DoMovement(dt);
3587}
3588
3590 /* if we get here quickly assume 1ms so that some movement occurs */
3591 if (dt == 0) dt = 1;
3592
3593 m_mustmove -= dt;
3594 if (m_mustmove < 0) m_mustmove = 0;
3595
3596 if (!m_inPinch) { // this stops compound zoom/pan
3597 if (m_pan_drag.x || m_pan_drag.y) {
3598 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3599 m_pan_drag.x = m_pan_drag.y = 0;
3600 }
3601
3602 if (m_panx || m_pany) {
3603 const double slowpan = .1, maxpan = 2;
3604 if (m_modkeys == wxMOD_ALT)
3605 m_panspeed = slowpan;
3606 else {
3607 m_panspeed += (double)dt / 500; /* apply acceleration */
3608 m_panspeed = wxMin(maxpan, m_panspeed);
3609 }
3610 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3611 }
3612 }
3613 if (m_zoom_factor != 1) {
3614 double alpha = 400, beta = 1.5;
3615 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3616
3617 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3618
3619 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3620
3621 // Try to hit the zoom target exactly.
3622 // if(m_wheelzoom_stop_oneshot > 0)
3623 {
3624 if (zoom_factor > 1) {
3625 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3626 zoom_factor = VPoint.chart_scale / m_zoom_target;
3627 }
3628
3629 else if (zoom_factor < 1) {
3630 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3631 zoom_factor = VPoint.chart_scale / m_zoom_target;
3632 }
3633 }
3634
3635 if (fabs(zoom_factor - 1) > 1e-4) {
3636 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3637 } else {
3638 StopMovement();
3639 }
3640
3641 if (m_wheelzoom_stop_oneshot > 0) {
3642 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3643 m_wheelzoom_stop_oneshot = 0;
3644 StopMovement();
3645 }
3646
3647 // Don't overshoot the zoom target.
3648 if (zoom_factor > 1) {
3649 if (VPoint.chart_scale <= m_zoom_target) {
3650 m_wheelzoom_stop_oneshot = 0;
3651 StopMovement();
3652 }
3653 } else if (zoom_factor < 1) {
3654 if (VPoint.chart_scale >= m_zoom_target) {
3655 m_wheelzoom_stop_oneshot = 0;
3656 StopMovement();
3657 }
3658 }
3659 }
3660 }
3661
3662 if (m_rotation_speed) { /* in degrees per second */
3663 double speed = m_rotation_speed;
3664 if (m_modkeys == wxMOD_ALT) speed /= 10;
3665 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3666 }
3667}
3668
3669void ChartCanvas::SetColorScheme(ColorScheme cs) {
3670 SetAlertString("");
3671
3672 // Setup ownship image pointers
3673 switch (cs) {
3674 case GLOBAL_COLOR_SCHEME_DAY:
3675 m_pos_image_red = &m_os_image_red_day;
3676 m_pos_image_grey = &m_os_image_grey_day;
3677 m_pos_image_yellow = &m_os_image_yellow_day;
3678 m_pos_image_user = m_pos_image_user_day;
3679 m_pos_image_user_grey = m_pos_image_user_grey_day;
3680 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3681 m_cTideBitmap = m_bmTideDay;
3682 m_cCurrentBitmap = m_bmCurrentDay;
3683
3684 break;
3685 case GLOBAL_COLOR_SCHEME_DUSK:
3686 m_pos_image_red = &m_os_image_red_dusk;
3687 m_pos_image_grey = &m_os_image_grey_dusk;
3688 m_pos_image_yellow = &m_os_image_yellow_dusk;
3689 m_pos_image_user = m_pos_image_user_dusk;
3690 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3691 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3692 m_cTideBitmap = m_bmTideDusk;
3693 m_cCurrentBitmap = m_bmCurrentDusk;
3694 break;
3695 case GLOBAL_COLOR_SCHEME_NIGHT:
3696 m_pos_image_red = &m_os_image_red_night;
3697 m_pos_image_grey = &m_os_image_grey_night;
3698 m_pos_image_yellow = &m_os_image_yellow_night;
3699 m_pos_image_user = m_pos_image_user_night;
3700 m_pos_image_user_grey = m_pos_image_user_grey_night;
3701 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3702 m_cTideBitmap = m_bmTideNight;
3703 m_cCurrentBitmap = m_bmCurrentNight;
3704 break;
3705 default:
3706 m_pos_image_red = &m_os_image_red_day;
3707 m_pos_image_grey = &m_os_image_grey_day;
3708 m_pos_image_yellow = &m_os_image_yellow_day;
3709 m_pos_image_user = m_pos_image_user_day;
3710 m_pos_image_user_grey = m_pos_image_user_grey_day;
3711 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3712 m_cTideBitmap = m_bmTideDay;
3713 m_cCurrentBitmap = m_bmCurrentDay;
3714 break;
3715 }
3716
3717 CreateDepthUnitEmbossMaps(cs);
3718 CreateOZEmbossMapData(cs);
3719
3720 // Set up fog effect base color
3721 m_fog_color = wxColor(
3722 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3723 float dim = 1.0;
3724 switch (cs) {
3725 case GLOBAL_COLOR_SCHEME_DUSK:
3726 dim = 0.5;
3727 break;
3728 case GLOBAL_COLOR_SCHEME_NIGHT:
3729 dim = 0.25;
3730 break;
3731 default:
3732 break;
3733 }
3734 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3735 m_fog_color.Blue() * dim);
3736
3737 // Really dark
3738#if 0
3739 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3740 SetBackgroundColour( wxColour(0,0,0) );
3741
3742 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3743 }
3744 else{
3745 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3746#ifndef __WXMAC__
3747 SetBackgroundColour( wxNullColour );
3748#endif
3749 }
3750#endif
3751
3752 // UpdateToolbarColorScheme(cs);
3753
3754 m_Piano->SetColorScheme(cs);
3755
3756 m_Compass->SetColorScheme(cs);
3757
3758 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3759
3760 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3761
3762 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3763 if (m_notification_button) {
3764 m_notification_button->SetColorScheme(cs);
3765 }
3766
3767#ifdef ocpnUSE_GL
3768 if (g_bopengl && m_glcc) {
3769 m_glcc->SetColorScheme(cs);
3770 g_glTextureManager->ClearAllRasterTextures();
3771 // m_glcc->FlushFBO();
3772 }
3773#endif
3774 SetbTCUpdate(true); // force re-render of tide/current locators
3775 m_brepaint_piano = true;
3776
3777 ReloadVP();
3778
3779 m_cs = cs;
3780}
3781
3782wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3783 wxImage img = Bitmap.ConvertToImage();
3784 int sx = img.GetWidth();
3785 int sy = img.GetHeight();
3786
3787 wxImage new_img(img);
3788
3789 for (int i = 0; i < sx; i++) {
3790 for (int j = 0; j < sy; j++) {
3791 if (!img.IsTransparent(i, j)) {
3792 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3793 (unsigned char)(img.GetGreen(i, j) * factor),
3794 (unsigned char)(img.GetBlue(i, j) * factor));
3795 }
3796 }
3797 }
3798
3799 wxBitmap ret = wxBitmap(new_img);
3800
3801 return ret;
3802}
3803
3804void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3805 int max) {
3806 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3807 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3808
3809 if (!m_pBrightPopup) {
3810 // Calculate size
3811 int x, y;
3812 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3813
3814 m_pBrightPopup = new TimedPopupWin(this, 3);
3815
3816 m_pBrightPopup->SetSize(x, y);
3817 m_pBrightPopup->Move(120, 120);
3818 }
3819
3820 int bmpsx = m_pBrightPopup->GetSize().x;
3821 int bmpsy = m_pBrightPopup->GetSize().y;
3822
3823 wxBitmap bmp(bmpsx, bmpsx);
3824 wxMemoryDC mdc(bmp);
3825
3826 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3827 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3828 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3829 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3830 mdc.Clear();
3831
3832 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3833
3834 mdc.SetFont(*pfont);
3835 wxString val;
3836
3837 if (brightness == max)
3838 val = "MAX";
3839 else if (brightness == min)
3840 val = "MIN";
3841 else
3842 val.Printf("%3d", brightness);
3843
3844 mdc.DrawText(val, 0, 0);
3845
3846 mdc.SelectObject(wxNullBitmap);
3847
3848 m_pBrightPopup->SetBitmap(bmp);
3849 m_pBrightPopup->Show();
3850 m_pBrightPopup->Refresh();
3851}
3852
3853void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3854 m_b_rot_hidef = true;
3855 ReloadVP();
3856}
3857
3858void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3859 if (!g_bRollover) return;
3860
3861 bool b_need_refresh = false;
3862
3863 wxSize win_size = GetSize() * m_displayScale;
3864 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3865
3866 // Handle the AIS Rollover Window first
3867 bool showAISRollover = false;
3868 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3869 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3870 SelectItem *pFind = pSelectAIS->FindSelection(
3871 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3872 if (pFind) {
3873 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3874 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3875
3876 if (ptarget) {
3877 showAISRollover = true;
3878
3879 if (NULL == m_pAISRolloverWin) {
3880 m_pAISRolloverWin = new RolloverWin(this);
3881 m_pAISRolloverWin->IsActive(false);
3882 b_need_refresh = true;
3883 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3884 m_AISRollover_MMSI != FoundAIS_MMSI) {
3885 // Sometimes the mouse moves fast enough to get over a new AIS
3886 // target before the one-shot has fired to remove the old target.
3887 // Result: wrong target data is shown.
3888 // Detect this case,close the existing rollover ASAP, and restart
3889 // the timer.
3890 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3891 m_pAISRolloverWin->IsActive(false);
3892 m_AISRollover_MMSI = 0;
3893 Refresh();
3894 return;
3895 }
3896
3897 m_AISRollover_MMSI = FoundAIS_MMSI;
3898
3899 if (!m_pAISRolloverWin->IsActive()) {
3900 wxString s = ptarget->GetRolloverString();
3901 m_pAISRolloverWin->SetString(s);
3902
3903 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3904 AIS_ROLLOVER, win_size);
3905 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3906 m_pAISRolloverWin->IsActive(true);
3907 b_need_refresh = true;
3908 }
3909 }
3910 } else {
3911 m_AISRollover_MMSI = 0;
3912 showAISRollover = false;
3913 }
3914 }
3915
3916 // Maybe turn the rollover off
3917 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3918 m_pAISRolloverWin->IsActive(false);
3919 m_AISRollover_MMSI = 0;
3920 b_need_refresh = true;
3921 }
3922
3923 // Now the Route info rollover
3924 // Show the route segment info
3925 bool showRouteRollover = false;
3926
3927 if (NULL == m_pRolloverRouteSeg) {
3928 // Get a list of all selectable sgements, and search for the first
3929 // visible segment as the rollover target.
3930
3931 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3932 SelectableItemList SelList = pSelect->FindSelectionList(
3933 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3934 auto node = SelList.begin();
3935 while (node != SelList.end()) {
3936 SelectItem *pFindSel = *node;
3937
3938 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3939
3940 if (pr && pr->IsVisible()) {
3941 m_pRolloverRouteSeg = pFindSel;
3942 showRouteRollover = true;
3943
3944 if (NULL == m_pRouteRolloverWin) {
3945 m_pRouteRolloverWin = new RolloverWin(this, 10);
3946 m_pRouteRolloverWin->IsActive(false);
3947 }
3948
3949 if (!m_pRouteRolloverWin->IsActive()) {
3950 wxString s;
3951 RoutePoint *segShow_point_a =
3952 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
3953 RoutePoint *segShow_point_b =
3954 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
3955
3956 double brg, dist;
3957 DistanceBearingMercator(
3958 segShow_point_b->m_lat, segShow_point_b->m_lon,
3959 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
3960
3961 if (!pr->m_bIsInLayer)
3962 s.Append(_("Route") + ": ");
3963 else
3964 s.Append(_("Layer Route: "));
3965
3966 if (pr->m_RouteNameString.IsEmpty())
3967 s.Append(_("(unnamed)"));
3968 else
3969 s.Append(pr->m_RouteNameString);
3970
3971 s << "\n"
3972 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
3973 << "\n"
3974 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
3975 << segShow_point_b->GetName() << "\n";
3976
3977 if (g_bShowTrue)
3978 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
3979 (int)floor(brg + 0.5), 0x00B0);
3980 if (g_bShowMag) {
3981 double latAverage =
3982 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
3983 double lonAverage =
3984 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
3985 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
3986
3987 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
3988 (int)floor(varBrg + 0.5), 0x00B0);
3989 }
3990
3991 s << FormatDistanceAdaptive(dist);
3992
3993 // Compute and display cumulative distance from route start point to
3994 // current leg end point and RNG,TTG,ETA from ship to current leg end
3995 // point for active route
3996 double shiptoEndLeg = 0.;
3997 bool validActive = false;
3998 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
3999 validActive = true;
4000
4001 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4002 auto node = pr->pRoutePointList->begin();
4003 RoutePoint *prp;
4004 float dist_to_endleg = 0;
4005 wxString t;
4006
4007 for (++node; node != pr->pRoutePointList->end(); ++node) {
4008 prp = *node;
4009 if (validActive)
4010 shiptoEndLeg += prp->m_seg_len;
4011 else if (prp->m_bIsActive)
4012 validActive = true;
4013 dist_to_endleg += prp->m_seg_len;
4014 if (prp->IsSame(segShow_point_a)) break;
4015 }
4016 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4017 }
4018 // write from ship to end selected leg point data if the route is
4019 // active
4020 if (validActive) {
4021 s << "\n"
4022 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4023 shiptoEndLeg +=
4025 ->GetCurrentRngToActivePoint(); // add distance from ship
4026 // to active point
4027 shiptoEndLeg +=
4028 segShow_point_b
4029 ->m_seg_len; // add the lenght of the selected leg
4030 s << FormatDistanceAdaptive(shiptoEndLeg);
4031 // ensure sog/cog are valid and vmg is positive to keep data
4032 // coherent
4033 double vmg = 0.;
4034 if (!std::isnan(gCog) && !std::isnan(gSog))
4035 vmg = gSog *
4036 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4037 PI / 180.);
4038 if (vmg > 0.) {
4039 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4040 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4041 s << " - "
4042 << wxString(ttg_sec > SECONDS_PER_DAY
4043 ? ttg_span.Format(_("%Dd %H:%M"))
4044 : ttg_span.Format(_("%H:%M")));
4045 wxDateTime dtnow, eta;
4046 eta = dtnow.SetToCurrent().Add(ttg_span);
4047 s << " - " << eta.Format("%b").Mid(0, 4)
4048 << eta.Format(" %d %H:%M");
4049 } else
4050 s << " ---- ----";
4051 }
4052 m_pRouteRolloverWin->SetString(s);
4053
4054 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4055 LEG_ROLLOVER, win_size);
4056 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4057 m_pRouteRolloverWin->IsActive(true);
4058 b_need_refresh = true;
4059 showRouteRollover = true;
4060 break;
4061 }
4062 } else {
4063 ++node;
4064 }
4065 }
4066 } else {
4067 // Is the cursor still in select radius, and not timed out?
4068 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4069 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4070 m_pRolloverRouteSeg))
4071 showRouteRollover = false;
4072 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4073 showRouteRollover = false;
4074 else
4075 showRouteRollover = true;
4076 }
4077
4078 // If currently creating a route, do not show this rollover window
4079 if (m_routeState) showRouteRollover = false;
4080
4081 // Similar for AIS target rollover window
4082 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4083 showRouteRollover = false;
4084
4085 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4086 !showRouteRollover) {
4087 m_pRouteRolloverWin->IsActive(false);
4088 m_pRolloverRouteSeg = NULL;
4089 m_pRouteRolloverWin->Destroy();
4090 m_pRouteRolloverWin = NULL;
4091 b_need_refresh = true;
4092 } else if (m_pRouteRolloverWin && showRouteRollover) {
4093 m_pRouteRolloverWin->IsActive(true);
4094 b_need_refresh = true;
4095 }
4096
4097 // Now the Track info rollover
4098 // Show the track segment info
4099 bool showTrackRollover = false;
4100
4101 if (NULL == m_pRolloverTrackSeg) {
4102 // Get a list of all selectable sgements, and search for the first
4103 // visible segment as the rollover target.
4104
4105 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4106 SelectableItemList SelList = pSelect->FindSelectionList(
4107 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4108
4109 auto node = SelList.begin();
4110 while (node != SelList.end()) {
4111 SelectItem *pFindSel = *node;
4112
4113 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4114
4115 if (pt && pt->IsVisible()) {
4116 m_pRolloverTrackSeg = pFindSel;
4117 showTrackRollover = true;
4118
4119 if (NULL == m_pTrackRolloverWin) {
4120 m_pTrackRolloverWin = new RolloverWin(this, 10);
4121 m_pTrackRolloverWin->IsActive(false);
4122 }
4123
4124 if (!m_pTrackRolloverWin->IsActive()) {
4125 wxString s;
4126 TrackPoint *segShow_point_a =
4127 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4128 TrackPoint *segShow_point_b =
4129 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4130
4131 double brg, dist;
4132 DistanceBearingMercator(
4133 segShow_point_b->m_lat, segShow_point_b->m_lon,
4134 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4135
4136 if (!pt->m_bIsInLayer)
4137 s.Append(_("Track") + ": ");
4138 else
4139 s.Append(_("Layer Track: "));
4140
4141 if (pt->GetName().IsEmpty())
4142 s.Append(_("(unnamed)"));
4143 else
4144 s.Append(pt->GetName());
4145 double tlenght = pt->Length();
4146 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4147 if (pt->GetLastPoint()->GetTimeString() &&
4148 pt->GetPoint(0)->GetTimeString()) {
4149 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4150 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4151 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4152 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4153 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4154 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4155 << getUsrSpeedUnit();
4156 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4157 : ttime.Format(" %H:%M"));
4158 }
4159 }
4160
4161 if (g_bShowTrackPointTime &&
4162 strlen(segShow_point_b->GetTimeString())) {
4163 wxString stamp = segShow_point_b->GetTimeString();
4164 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4165 if (timestamp.IsValid()) {
4166 // Format track rollover timestamp to OCPN global TZ setting
4169 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4170 }
4171 s << "\n" << _("Segment Created: ") << stamp;
4172 }
4173
4174 s << "\n";
4175 if (g_bShowTrue)
4176 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4177 0x00B0);
4178
4179 if (g_bShowMag) {
4180 double latAverage =
4181 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4182 double lonAverage =
4183 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4184 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4185
4186 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4187 0x00B0);
4188 }
4189
4190 s << FormatDistanceAdaptive(dist);
4191
4192 if (segShow_point_a->GetTimeString() &&
4193 segShow_point_b->GetTimeString()) {
4194 wxDateTime apoint = segShow_point_a->GetCreateTime();
4195 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4196 if (apoint.IsValid() && bpoint.IsValid()) {
4197 double segmentSpeed = toUsrSpeed(
4198 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4199 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4200 << getUsrSpeedUnit();
4201 }
4202 }
4203
4204 m_pTrackRolloverWin->SetString(s);
4205
4206 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4207 LEG_ROLLOVER, win_size);
4208 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4209 m_pTrackRolloverWin->IsActive(true);
4210 b_need_refresh = true;
4211 showTrackRollover = true;
4212 break;
4213 }
4214 } else {
4215 ++node;
4216 }
4217 }
4218 } else {
4219 // Is the cursor still in select radius, and not timed out?
4220 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4221 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4222 m_pRolloverTrackSeg))
4223 showTrackRollover = false;
4224 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4225 showTrackRollover = false;
4226 else
4227 showTrackRollover = true;
4228 }
4229
4230 // Similar for AIS target rollover window
4231 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4232 showTrackRollover = false;
4233
4234 // Similar for route rollover window
4235 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4236 showTrackRollover = false;
4237
4238 // TODO We onlt show tracks on primary canvas....
4239 // if(!IsPrimaryCanvas())
4240 // showTrackRollover = false;
4241
4242 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4243 !showTrackRollover) {
4244 m_pTrackRolloverWin->IsActive(false);
4245 m_pRolloverTrackSeg = NULL;
4246 m_pTrackRolloverWin->Destroy();
4247 m_pTrackRolloverWin = NULL;
4248 b_need_refresh = true;
4249 } else if (m_pTrackRolloverWin && showTrackRollover) {
4250 m_pTrackRolloverWin->IsActive(true);
4251 b_need_refresh = true;
4252 }
4253
4254 if (b_need_refresh) Refresh();
4255}
4256
4257void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4258 if ((GetShowENCLights() || m_bsectors_shown) &&
4259 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4260 extendedSectorLegs)) {
4261 if (!m_bsectors_shown) {
4262 ReloadVP(false);
4263 m_bsectors_shown = true;
4264 }
4265 } else {
4266 if (m_bsectors_shown) {
4267 ReloadVP(false);
4268 m_bsectors_shown = false;
4269 }
4270 }
4271
4272// This is here because GTK status window update is expensive..
4273// cairo using pango rebuilds the font every time so is very
4274// inefficient
4275// Anyway, only update the status bar when this timer expires
4276#if defined(__WXGTK__) || defined(__WXQT__)
4277 {
4278 // Check the absolute range of the cursor position
4279 // There could be a window wherein the chart geoereferencing is not
4280 // valid....
4281 double cursor_lat, cursor_lon;
4282 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4283
4284 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4285 while (cursor_lon < -180.) cursor_lon += 360.;
4286
4287 while (cursor_lon > 180.) cursor_lon -= 360.;
4288
4289 SetCursorStatus(cursor_lat, cursor_lon);
4290 }
4291 }
4292#endif
4293}
4294
4295void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4296 if (!parent_frame->m_pStatusBar) return;
4297
4298 wxString s1;
4299 s1 += " ";
4300 s1 += toSDMM(1, cursor_lat);
4301 s1 += " ";
4302 s1 += toSDMM(2, cursor_lon);
4303
4304 if (STAT_FIELD_CURSOR_LL >= 0)
4305 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4306
4307 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4308
4309 double brg, dist;
4310 wxString sm;
4311 wxString st;
4312 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4313 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4314 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4315
4316 wxString s = st + sm;
4317 s << FormatDistanceAdaptive(dist);
4318
4319 // CUSTOMIZATION - LIVE ETA OPTION
4320 // -------------------------------------------------------
4321 // Calculate an "live" ETA based on route starting from the current
4322 // position of the boat and goes to the cursor of the mouse.
4323 // In any case, an standard ETA will be calculated with a default speed
4324 // of the boat to give an estimation of the route (in particular if GPS
4325 // is off).
4326
4327 // Display only if option "live ETA" is selected in Settings > Display >
4328 // General.
4329 if (g_bShowLiveETA) {
4330 float realTimeETA;
4331 float boatSpeed;
4332 float boatSpeedDefault = g_defaultBoatSpeed;
4333
4334 // Calculate Estimate Time to Arrival (ETA) in minutes
4335 // Check before is value not closed to zero (it will make an very big
4336 // number...)
4337 if (!std::isnan(gSog)) {
4338 boatSpeed = gSog;
4339 if (boatSpeed < 0.5) {
4340 realTimeETA = 0;
4341 } else {
4342 realTimeETA = dist / boatSpeed * 60;
4343 }
4344 } else {
4345 realTimeETA = 0;
4346 }
4347
4348 // Add space after distance display
4349 s << " ";
4350 // Display ETA
4351 s << minutesToHoursDays(realTimeETA);
4352
4353 // In any case, display also an ETA with default speed at 6knts
4354
4355 s << " [@";
4356 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4357 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4358 s << " ";
4359 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4360 s << "]";
4361 }
4362 // END OF - LIVE ETA OPTION
4363
4364 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4365}
4366
4367// CUSTOMIZATION - FORMAT MINUTES
4368// -------------------------------------------------------
4369// New function to format minutes into a more readable format:
4370// * Hours + minutes, or
4371// * Days + hours.
4372wxString minutesToHoursDays(float timeInMinutes) {
4373 wxString s;
4374
4375 if (timeInMinutes == 0) {
4376 s << "--min";
4377 }
4378
4379 // Less than 60min, keep time in minutes
4380 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4381 s << wxString::Format("%d", (int)timeInMinutes);
4382 s << "min";
4383 }
4384
4385 // Between 1h and less than 24h, display time in hours, minutes
4386 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4387 int hours;
4388 int min;
4389 hours = (int)timeInMinutes / 60;
4390 min = (int)timeInMinutes % 60;
4391
4392 if (min == 0) {
4393 s << wxString::Format("%d", hours);
4394 s << "h";
4395 } else {
4396 s << wxString::Format("%d", hours);
4397 s << "h";
4398 s << wxString::Format("%d", min);
4399 s << "min";
4400 }
4401
4402 }
4403
4404 // More than 24h, display time in days, hours
4405 else if (timeInMinutes > 24 * 60) {
4406 int days;
4407 int hours;
4408 days = (int)(timeInMinutes / 60) / 24;
4409 hours = (int)(timeInMinutes / 60) % 24;
4410
4411 if (hours == 0) {
4412 s << wxString::Format("%d", days);
4413 s << "d";
4414 } else {
4415 s << wxString::Format("%d", days);
4416 s << "d";
4417 s << wxString::Format("%d", hours);
4418 s << "h";
4419 }
4420 }
4421
4422 return s;
4423}
4424
4425// END OF CUSTOMIZATION - FORMAT MINUTES
4426// Thanks open source code ;-)
4427// -------------------------------------------------------
4428
4429void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4430 double clat, clon;
4431 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4432 *lat = clat;
4433 *lon = clon;
4434}
4435
4436void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4437 wxPoint2DDouble *r) {
4438 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4439}
4440
4442 double rlon, wxPoint2DDouble *r) {
4443 // If the Current Chart is a raster chart, and the
4444 // requested lat/long is within the boundaries of the chart,
4445 // and the VP is not rotated,
4446 // then use the embedded BSB chart georeferencing algorithm
4447 // for greater accuracy
4448 // Additionally, use chart embedded georef if the projection is TMERC
4449 // i.e. NOT MERCATOR and NOT POLYCONIC
4450
4451 // If for some reason the chart rejects the request by returning an error,
4452 // then fall back to Viewport Projection estimate from canvas parameters
4453 if (!g_bopengl && m_singleChart &&
4454 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4455 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4456 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4457 (m_singleChart->GetChartProjectionType() !=
4458 PROJECTION_TRANSVERSE_MERCATOR) &&
4459 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4460 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4461 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4462 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4463 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4464 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4465 // Cur_BSB_Ch->GetCOVRTablenPoints
4466 // ( 0 ), rlon,
4467 // rlat );
4468 // bInside = true;
4469 // if ( bInside )
4470 if (Cur_BSB_Ch) {
4471 // This is a Raster chart....
4472 // If the VP is changing, the raster chart parameters may not yet be
4473 // setup So do that before accessing the chart's embedded
4474 // georeferencing
4475 Cur_BSB_Ch->SetVPRasterParms(vp);
4476 double rpixxd, rpixyd;
4477 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4478 r->m_x = rpixxd;
4479 r->m_y = rpixyd;
4480 return;
4481 }
4482 }
4483 }
4484
4485 // if needed, use the VPoint scaling estimator,
4486 *r = vp.GetDoublePixFromLL(rlat, rlon);
4487}
4488
4489// This routine might be deleted and all of the rendering improved
4490// to have floating point accuracy
4491bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4492 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4493}
4494
4495bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4496 wxPoint *r) {
4497 wxPoint2DDouble p;
4498 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4499
4500 // some projections give nan values when invisible values (other side of
4501 // world) are requested we should stop using integer coordinates or return
4502 // false here (and test it everywhere)
4503 if (std::isnan(p.m_x)) {
4504 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4505 return false;
4506 }
4507
4508 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4509 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4510 else
4511 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4512
4513 return true;
4514}
4515
4516void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4517 double &lon) {
4518 // If the Current Chart is a raster chart, and the
4519 // requested x,y is within the boundaries of the chart,
4520 // and the VP is not rotated,
4521 // then use the embedded BSB chart georeferencing algorithm
4522 // for greater accuracy
4523 // Additionally, use chart embedded georef if the projection is TMERC
4524 // i.e. NOT MERCATOR and NOT POLYCONIC
4525
4526 // If for some reason the chart rejects the request by returning an error,
4527 // then fall back to Viewport Projection estimate from canvas parameters
4528 bool bUseVP = true;
4529
4530 if (!g_bopengl && m_singleChart &&
4531 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4532 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4533 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4534 (m_singleChart->GetChartProjectionType() !=
4535 PROJECTION_TRANSVERSE_MERCATOR) &&
4536 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4537 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4538 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4539 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4540
4541 // TODO maybe need iterative process to validate bInside
4542 // first pass is mercator, then check chart boundaries
4543
4544 if (Cur_BSB_Ch) {
4545 // This is a Raster chart....
4546 // If the VP is changing, the raster chart parameters may not yet be
4547 // setup So do that before accessing the chart's embedded
4548 // georeferencing
4549 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4550
4551 double slat, slon;
4552 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4553 lat = slat;
4554
4555 if (slon < -180.)
4556 slon += 360.;
4557 else if (slon > 180.)
4558 slon -= 360.;
4559
4560 lon = slon;
4561 bUseVP = false;
4562 }
4563 }
4564 }
4565
4566 // if needed, use the VPoint scaling estimator
4567 if (bUseVP) {
4568 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4569 }
4570}
4571
4573 StopMovement();
4574 DoZoomCanvas(factor, false);
4575 extendedSectorLegs.clear();
4576}
4577
4578void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4579 bool stoptimer) {
4580 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4581
4582 if (g_bsmoothpanzoom) {
4583 if (StartTimedMovement(stoptimer)) {
4584 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4585 m_zoom_factor = factor;
4586 }
4587
4588 m_zoom_target = VPoint.chart_scale / factor;
4589 } else {
4590 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4591
4592 DoZoomCanvas(factor, can_zoom_to_cursor);
4593 }
4594
4595 extendedSectorLegs.clear();
4596}
4597
4598void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4599 // possible on startup
4600 if (!ChartData) return;
4601 if (!m_pCurrentStack) return;
4602
4603 /* TODO: queue the quilted loading code to a background thread
4604 so yield is never called from here, and also rendering is not delayed */
4605
4606 // Cannot allow Yield() re-entrancy here
4607 if (m_bzooming) return;
4608 m_bzooming = true;
4609
4610 double old_ppm = GetVP().view_scale_ppm;
4611
4612 // Capture current cursor position for zoom to cursor
4613 double zlat = m_cursor_lat;
4614 double zlon = m_cursor_lon;
4615
4616 double proposed_scale_onscreen =
4617 GetVP().chart_scale /
4618 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4619 bool b_do_zoom = false;
4620
4621 if (factor > 1) {
4622 b_do_zoom = true;
4623
4624 // double zoom_factor = factor;
4625
4626 ChartBase *pc = NULL;
4627
4628 if (!VPoint.b_quilt) {
4629 pc = m_singleChart;
4630 } else {
4631 if (!m_disable_adjust_on_zoom) {
4632 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4633 if (new_db_index >= 0)
4634 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4635 else { // for whatever reason, no reference chart is known
4636 // Choose the smallest scale chart on the current stack
4637 // and then adjust for scale range
4638 int current_ref_stack_index = -1;
4639 if (m_pCurrentStack->nEntry) {
4640 int trial_index =
4641 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4642 m_pQuilt->SetReferenceChart(trial_index);
4643 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4644 if (new_db_index >= 0)
4645 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4646 }
4647 }
4648
4649 if (m_pCurrentStack)
4650 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4651 new_db_index); // highlite the correct bar entry
4652 }
4653 }
4654
4655 if (pc) {
4656 // double target_scale_ppm = GetVPScale() * zoom_factor;
4657 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4658 // target_scale_ppm;
4659
4660 // Query the chart to determine the appropriate zoom range
4661 double min_allowed_scale =
4662 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4663
4664 if (proposed_scale_onscreen < min_allowed_scale) {
4665 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4666 m_zoom_factor = 1; /* stop zooming */
4667 b_do_zoom = false;
4668 } else
4669 proposed_scale_onscreen = min_allowed_scale;
4670 }
4671
4672 } else {
4673 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4674 }
4675
4676 } else if (factor < 1) {
4677 b_do_zoom = true;
4678
4679 ChartBase *pc = NULL;
4680
4681 bool b_smallest = false;
4682
4683 if (!VPoint.b_quilt) { // not quilted
4684 pc = m_singleChart;
4685
4686 if (pc) {
4687 // If m_singleChart is not on the screen, unbound the zoomout
4688 LLBBox viewbox = VPoint.GetBBox();
4689 // BoundingBox chart_box;
4690 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4691 double max_allowed_scale;
4692
4693 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4694
4695 // We can allow essentially unbounded zoomout in single chart mode
4696 // if( ChartData->GetDBBoundingBox( current_index,
4697 // &chart_box ) &&
4698 // !viewbox.IntersectOut( chart_box ) )
4699 // // Clamp the minimum scale zoom-out to the value
4700 // specified by the chart max_allowed_scale =
4701 // wxMin(max_allowed_scale, 4.0 *
4702 // pc->GetNormalScaleMax(
4703 // GetCanvasScaleFactor(),
4704 // GetCanvasWidth() ) );
4705 if (proposed_scale_onscreen > max_allowed_scale) {
4706 m_zoom_factor = 1; /* stop zooming */
4707 proposed_scale_onscreen = max_allowed_scale;
4708 }
4709 }
4710
4711 } else {
4712 if (!m_disable_adjust_on_zoom) {
4713 int new_db_index =
4714 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4715 if (new_db_index >= 0)
4716 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4717
4718 if (m_pCurrentStack)
4719 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4720 new_db_index); // highlite the correct bar entry
4721
4722 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4723
4724 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4725 proposed_scale_onscreen =
4726 wxMin(proposed_scale_onscreen,
4727 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4728 }
4729
4730 // set a minimum scale
4731 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4732 m_absolute_min_scale_ppm)
4733 proposed_scale_onscreen =
4734 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4735 }
4736 }
4737 double new_scale =
4738 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4739
4740 if (b_do_zoom) {
4741 // Disable ZTC if lookahead is ON, and currently b_follow is active
4742 bool b_allow_ztc = true;
4743 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4744 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4745 if (m_bLookAhead) {
4746 double brg, distance;
4747 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4748 &distance);
4749 dir_to_shift = brg;
4750 meters_to_shift = distance * 1852;
4751 }
4752 // Arrange to combine the zoom and pan into one operation for smoother
4753 // appearance
4754 SetVPScale(new_scale, false); // adjust, but deferred refresh
4755 wxPoint r;
4756 GetCanvasPointPix(zlat, zlon, &r);
4757 // this will emit the Refresh()
4758 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4759 } else {
4760 SetVPScale(new_scale);
4761 if (m_bFollow) DoCanvasUpdate();
4762 }
4763 }
4764
4765 m_bzooming = false;
4766}
4767
4768void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4769 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4770 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4771}
4772
4773int rot;
4774void ChartCanvas::RotateCanvas(double dir) {
4775 // SetUpMode(NORTH_UP_MODE);
4776
4777 if (g_bsmoothpanzoom) {
4778 if (StartTimedMovement()) {
4779 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4780 m_rotation_speed = dir * 60;
4781 }
4782 } else {
4783 double speed = dir * 10;
4784 if (m_modkeys == wxMOD_ALT) speed /= 20;
4785 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4786 }
4787}
4788
4789void ChartCanvas::DoRotateCanvas(double rotation) {
4790 while (rotation < 0) rotation += 2 * PI;
4791 while (rotation > 2 * PI) rotation -= 2 * PI;
4792
4793 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4794
4795 SetVPRotation(rotation);
4796 parent_frame->UpdateRotationState(VPoint.rotation);
4797}
4798
4799void ChartCanvas::DoTiltCanvas(double tilt) {
4800 while (tilt < 0) tilt = 0;
4801 while (tilt > .95) tilt = .95;
4802
4803 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4804
4805 VPoint.tilt = tilt;
4806 Refresh(false);
4807}
4808
4809void ChartCanvas::TogglebFollow() {
4810 if (!m_bFollow)
4811 SetbFollow();
4812 else
4813 ClearbFollow();
4814}
4815
4816void ChartCanvas::ClearbFollow() {
4817 m_bFollow = false; // update the follow flag
4818
4819 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4820
4821 UpdateFollowButtonState();
4822
4823 DoCanvasUpdate();
4824 ReloadVP();
4825 parent_frame->SetChartUpdatePeriod();
4826}
4827
4828void ChartCanvas::SetbFollow() {
4829 // Is the OWNSHIP on-screen?
4830 // If not, then reset the OWNSHIP offset to 0 (center screen)
4831 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4832 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4833 m_OSoffsetx = 0;
4834 m_OSoffsety = 0;
4835 }
4836
4837 // Apply the present b_follow offset values to ship position
4838 wxPoint2DDouble p;
4840 p.m_x += m_OSoffsetx;
4841 p.m_y -= m_OSoffsety;
4842
4843 // compute the target center screen lat/lon
4844 double dlat, dlon;
4845 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4846
4847 JumpToPosition(dlat, dlon, GetVPScale());
4848 m_bFollow = true;
4849
4850 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4851 UpdateFollowButtonState();
4852
4853 if (!g_bSmoothRecenter) {
4854 DoCanvasUpdate();
4855 ReloadVP();
4856 }
4857 parent_frame->SetChartUpdatePeriod();
4858}
4859
4860void ChartCanvas::UpdateFollowButtonState() {
4861 if (m_muiBar) {
4862 if (!m_bFollow)
4863 m_muiBar->SetFollowButtonState(0);
4864 else {
4865 if (m_bLookAhead)
4866 m_muiBar->SetFollowButtonState(2);
4867 else
4868 m_muiBar->SetFollowButtonState(1);
4869 }
4870 }
4871
4872#ifdef __ANDROID__
4873 if (!m_bFollow)
4874 androidSetFollowTool(0);
4875 else {
4876 if (m_bLookAhead)
4877 androidSetFollowTool(2);
4878 else
4879 androidSetFollowTool(1);
4880 }
4881#endif
4882
4883 // Look for plugin using API-121 or later
4884 // If found, make the follow state callback.
4885 if (g_pi_manager) {
4886 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4887 if (pic->m_enabled && pic->m_init_state) {
4888 switch (pic->m_api_version) {
4889 case 121: {
4890 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4891 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4892 break;
4893 }
4894 default:
4895 break;
4896 }
4897 }
4898 }
4899 }
4900}
4901
4902void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4903 if (g_bSmoothRecenter && !m_routeState) {
4904 if (StartSmoothJump(lat, lon, scale_ppm))
4905 return;
4906 else {
4907 // move closer to the target destination, and try again
4908 double gcDist, gcBearingEnd;
4909 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4910 &gcBearingEnd);
4911 gcBearingEnd += 180;
4912 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4913 GetCanvasWidth() / GetVPScale(); // meters
4914 double lon_offset =
4915 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4916 double new_lat = lat + (lat_offset / (1852 * 60));
4917 double new_lon = lon + (lon_offset / (1852 * 60));
4918 SetViewPoint(new_lat, new_lon);
4919 ReloadVP();
4920 StartSmoothJump(lat, lon, scale_ppm);
4921 return;
4922 }
4923 }
4924
4925 if (lon > 180.0) lon -= 360.0;
4926 m_vLat = lat;
4927 m_vLon = lon;
4928 StopMovement();
4929 m_bFollow = false;
4930
4931 if (!GetQuiltMode()) {
4932 double skew = 0;
4933 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4934 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4935 } else {
4936 if (scale_ppm != GetVPScale()) {
4937 // XXX should be done in SetViewPoint
4938 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4939 AdjustQuiltRefChart();
4940 }
4941 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4942 }
4943
4944 ReloadVP();
4945
4946 UpdateFollowButtonState();
4947
4948 // TODO
4949 // if( g_pi_manager ) {
4950 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4951 // }
4952}
4953
4954bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
4955 // Check distance to jump, in pixels at current chart scale
4956 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
4957 // width.
4958 double gcDist;
4959 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
4960 double distance_pixels = gcDist * GetVPScale();
4961 if (distance_pixels > 0.5 * GetCanvasWidth()) {
4962 // Jump is too far, try again
4963 return false;
4964 }
4965
4966 // Save where we're coming from
4967 m_startLat = m_vLat;
4968 m_startLon = m_vLon;
4969 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
4970
4971 // Save where we want to end up
4972 m_endLat = lat;
4973 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
4974 m_endScale = scale_ppm;
4975
4976 // Setup timing
4977 m_animationDuration = 600; // ms
4978 m_animationStart = wxGetLocalTimeMillis();
4979
4980 // Stop any previous movement, ensure no conflicts
4981 StopMovement();
4982 m_bFollow = false;
4983
4984 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
4985 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
4986 m_animationActive = true;
4987
4988 return true;
4989}
4990
4991void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
4992 // Calculate time fraction from 0..1
4993 wxLongLong now = wxGetLocalTimeMillis();
4994 double elapsed = (now - m_animationStart).ToDouble();
4995 double t = elapsed / m_animationDuration.ToDouble();
4996 if (t > 1.0) t = 1.0;
4997
4998 // Ease function for smoother movement
4999 double e = easeOutCubic(t);
5000
5001 // Interpolate lat/lon/scale
5002 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5003 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5004 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5005
5006 // Update viewpoint
5007 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5008 // portion)
5009 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5010 ReloadVP();
5011
5012 // If we reached the end, stop the timer and finalize
5013 if (t >= 1.0) {
5014 m_easeTimer.Stop();
5015 m_animationActive = false;
5016 UpdateFollowButtonState();
5017 ZoomCanvasSimple(1.0001);
5018 DoCanvasUpdate();
5019 ReloadVP();
5020 }
5021}
5022
5023bool ChartCanvas::PanCanvas(double dx, double dy) {
5024 if (!ChartData) return false;
5025 extendedSectorLegs.clear();
5026
5027 double dlat, dlon;
5028 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5029
5030 int iters = 0;
5031 for (;;) {
5032 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5033
5034 if (iters++ > 5) return false;
5035 if (!std::isnan(dlat)) break;
5036
5037 dx *= .5, dy *= .5;
5038 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5039 }
5040
5041 // avoid overshooting the poles
5042 if (dlat > 90)
5043 dlat = 90;
5044 else if (dlat < -90)
5045 dlat = -90;
5046
5047 if (dlon > 360.) dlon -= 360.;
5048 if (dlon < -360.) dlon += 360.;
5049
5050 // This should not really be necessary, but round-trip georef on some
5051 // charts is not perfect, So we can get creep on repeated unidimensional
5052 // pans, and corrupt chart cacheing.......
5053
5054 // But this only works on north-up projections
5055 // TODO: can we remove this now?
5056 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5057 // .001 ) ) {
5058 //
5059 // if( dx == 0 ) dlon = clon;
5060 // if( dy == 0 ) dlat = clat;
5061 // }
5062
5063 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5064
5065 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5066
5067 if (VPoint.b_quilt) {
5068 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5069 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5070 // Tweak the scale slightly for a new ref chart
5071 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5072 if (pc) {
5073 double tweak_scale_ppm =
5074 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5075 SetVPScale(tweak_scale_ppm);
5076 }
5077 }
5078
5079 if (new_ref_dbIndex == -1) {
5080#pragma GCC diagnostic push
5081#pragma GCC diagnostic ignored "-Warray-bounds"
5082 // The compiler sees a -1 index being used. Does not happen, though.
5083
5084 // for whatever reason, no reference chart is known
5085 // Probably panned out of the coverage region
5086 // If any charts are anywhere on-screen, choose the smallest
5087 // scale chart on the screen to be a new reference chart.
5088 int trial_index = -1;
5089 if (m_pCurrentStack->nEntry) {
5090 int trial_index =
5091 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5092 }
5093
5094 if (trial_index < 0) {
5095 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5096 if (full_screen_array.size())
5097 trial_index = full_screen_array[full_screen_array.size() - 1];
5098 }
5099
5100 if (trial_index >= 0) {
5101 m_pQuilt->SetReferenceChart(trial_index);
5102 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5103 VPoint.rotation);
5104 ReloadVP();
5105 }
5106#pragma GCC diagnostic pop
5107 }
5108 }
5109
5110 // Turn off bFollow only if the ownship has left the screen
5111 if (m_bFollow) {
5112 double offx, offy;
5113 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5114
5115 double offset_angle = atan2(offy, offx);
5116 double offset_distance = sqrt((offy * offy) + (offx * offx));
5117 double chart_angle = GetVPRotation();
5118 double target_angle = chart_angle - offset_angle;
5119 double d_east_mod = offset_distance * cos(target_angle);
5120 double d_north_mod = offset_distance * sin(target_angle);
5121
5122 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5123 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5124
5125 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5126 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5127 m_bFollow = false; // update the follow flag
5128 UpdateFollowButtonState();
5129 }
5130 }
5131
5132 Refresh(false);
5133
5134 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5135
5136 return true;
5137}
5138
5139bool ChartCanvas::IsOwnshipOnScreen() {
5140 wxPoint r;
5142 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5143 ((r.y > 0) && r.y < GetCanvasHeight()))
5144 return true;
5145 else
5146 return false;
5147}
5148
5149void ChartCanvas::ReloadVP(bool b_adjust) {
5150 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5151
5152 LoadVP(VPoint, b_adjust);
5153}
5154
5155void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5156#ifdef ocpnUSE_GL
5157 if (g_bopengl && m_glcc) {
5158 m_glcc->Invalidate();
5159 if (m_glcc->GetSize() != GetSize()) {
5160 m_glcc->SetSize(GetSize());
5161 }
5162 } else
5163#endif
5164 {
5165 m_cache_vp.Invalidate();
5166 m_bm_cache_vp.Invalidate();
5167 }
5168
5169 VPoint.Invalidate();
5170
5171 if (m_pQuilt) m_pQuilt->Invalidate();
5172
5173 // Make sure that the Selected Group is sensible...
5174 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5175 // m_groupIndex = 0;
5176 // if( !CheckGroup( m_groupIndex ) )
5177 // m_groupIndex = 0;
5178
5179 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5180 vp.m_projection_type, b_adjust);
5181}
5182
5183void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5184 m_pQuilt->SetReferenceChart(dbIndex);
5185 VPoint.Invalidate();
5186 m_pQuilt->Invalidate();
5187}
5188
5189double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5190 if (m_pQuilt)
5191 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5192 else
5193 return vp.view_scale_ppm;
5194}
5195
5196// Verify and adjust the current reference chart,
5197// so that it will not lead to excessive overzoom or underzoom onscreen
5198int ChartCanvas::AdjustQuiltRefChart() {
5199 int ret = -1;
5200 if (m_pQuilt) {
5201 wxASSERT(ChartData);
5202 ChartBase *pc =
5203 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5204 if (pc) {
5205 double min_ref_scale =
5206 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5207 double max_ref_scale =
5208 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5209
5210 if (VPoint.chart_scale < min_ref_scale) {
5211 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5212 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5213 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5214 } else {
5215 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5216
5217 if (!brender_ok) {
5218 int target_stack_index = wxNOT_FOUND;
5219 int il = 0;
5220 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5221 if (index == m_pQuilt->GetRefChartdbIndex()) {
5222 target_stack_index = il;
5223 break;
5224 }
5225 il++;
5226 }
5227 if (wxNOT_FOUND == target_stack_index) // should never happen...
5228 target_stack_index = 0;
5229
5230 int ref_family = pc->GetChartFamily();
5231 int extended_array_count =
5232 m_pQuilt->GetExtendedStackIndexArray().size();
5233 while ((!brender_ok) &&
5234 ((int)target_stack_index < (extended_array_count - 1))) {
5235 target_stack_index++;
5236 int test_db_index =
5237 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5238
5239 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5240 IsChartQuiltableRef(test_db_index)) {
5241 // open the target, and check the min_scale
5242 ChartBase *ptest_chart =
5243 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5244 if (ptest_chart) {
5245 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5246 }
5247 }
5248 }
5249
5250 if (brender_ok) { // found a better reference chart
5251 int new_db_index =
5252 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5253 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5254 IsChartQuiltableRef(new_db_index)) {
5255 m_pQuilt->SetReferenceChart(new_db_index);
5256 ret = new_db_index;
5257 } else
5258 ret = m_pQuilt->GetRefChartdbIndex();
5259 } else
5260 ret = m_pQuilt->GetRefChartdbIndex();
5261
5262 } else
5263 ret = m_pQuilt->GetRefChartdbIndex();
5264 }
5265 } else
5266 ret = -1;
5267 }
5268
5269 return ret;
5270}
5271
5272void ChartCanvas::UpdateCanvasOnGroupChange() {
5273 delete m_pCurrentStack;
5274 m_pCurrentStack = new ChartStack;
5275 wxASSERT(ChartData);
5276 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5277 m_groupIndex);
5278
5279 if (m_pQuilt) {
5280 m_pQuilt->Compose(VPoint);
5281 SetFocus();
5282 }
5283}
5284
5285bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5286 double latNE, double lonNE) {
5287 // Center Point
5288 double latc = (latSW + latNE) / 2.0;
5289 double lonc = (lonSW + lonNE) / 2.0;
5290
5291 // Get scale in ppm (latitude)
5292 double ne_easting, ne_northing;
5293 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5294
5295 double sw_easting, sw_northing;
5296 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5297
5298 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5299
5300 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5301}
5302
5303bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5304 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5305 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5306}
5307
5308bool ChartCanvas::SetVPProjection(int projection) {
5309 if (!g_bopengl) // alternative projections require opengl
5310 return false;
5311
5312 // the view scale varies depending on geographic location and projection
5313 // rescale to keep the relative scale on the screen the same
5314 double prev_true_scale_ppm = m_true_scale_ppm;
5315 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5316 VPoint.skew, VPoint.rotation, projection) &&
5317 SetVPScale(wxMax(
5318 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5319 m_absolute_min_scale_ppm));
5320}
5321
5322bool ChartCanvas::SetViewPoint(double lat, double lon) {
5323 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5324 VPoint.rotation);
5325}
5326
5327bool ChartCanvas::SetVPRotation(double angle) {
5328 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5329 VPoint.skew, angle);
5330}
5331bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5332 double skew, double rotation, int projection,
5333 bool b_adjust, bool b_refresh) {
5334 bool b_ret = false;
5335 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5336 skew -= 2 * PI;
5337 // Any sensible change?
5338 if (VPoint.IsValid()) {
5339 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5340 (fabs(VPoint.skew - skew) < 1e-9) &&
5341 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5342 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5343 (VPoint.m_projection_type == projection ||
5344 projection == PROJECTION_UNKNOWN))
5345 return false;
5346 }
5347 if (VPoint.m_projection_type != projection)
5348 VPoint.InvalidateTransformCache(); // invalidate
5349
5350 // Take a local copy of the last viewport
5351 ViewPort last_vp = VPoint;
5352
5353 VPoint.skew = skew;
5354 VPoint.clat = lat;
5355 VPoint.clon = lon;
5356 VPoint.rotation = rotation;
5357 VPoint.view_scale_ppm = scale_ppm;
5358 if (projection != PROJECTION_UNKNOWN)
5359 VPoint.SetProjectionType(projection);
5360 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5361 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5362
5363 // don't allow latitude above 88 for mercator (90 is infinity)
5364 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5365 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5366 if (VPoint.clat > 89.5)
5367 VPoint.clat = 89.5;
5368 else if (VPoint.clat < -89.5)
5369 VPoint.clat = -89.5;
5370 }
5371
5372 // don't zoom out too far for transverse mercator polyconic until we resolve
5373 // issues
5374 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5375 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5376 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5377
5378 // SetVPRotation(rotation);
5379
5380 if (!g_bopengl) // tilt is not possible without opengl
5381 VPoint.tilt = 0;
5382
5383 if ((VPoint.pix_width <= 0) ||
5384 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5385 return false;
5386
5387 bool bwasValid = VPoint.IsValid();
5388 VPoint.Validate(); // Mark this ViewPoint as OK
5389
5390 // Has the Viewport scale changed? If so, invalidate the vp
5391 if (last_vp.view_scale_ppm != scale_ppm) {
5392 m_cache_vp.Invalidate();
5393 InvalidateGL();
5394 }
5395
5396 // A preliminary value, may be tweaked below
5397 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5398
5399 // recompute cursor position
5400 // and send to interested plugins if the mouse is actually in this window
5401 int mouseX = mouse_x;
5402 int mouseY = mouse_y;
5403 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5404 (mouseY < VPoint.pix_height)) {
5405 double lat, lon;
5406 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5407 m_cursor_lat = lat;
5408 m_cursor_lon = lon;
5409 SendCursorLatLonToAllPlugIns(lat, lon);
5410 }
5411
5412 if (!VPoint.b_quilt && m_singleChart) {
5413 VPoint.SetBoxes();
5414
5415 // Allow the chart to adjust the new ViewPort for performance optimization
5416 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5417 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5418
5419 // If there is a sensible change in the chart render, refresh the whole
5420 // screen
5421 if ((!m_cache_vp.IsValid()) ||
5422 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5423 Refresh(false);
5424 b_ret = true;
5425 } else {
5426 wxPoint cp_last, cp_this;
5427 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5428 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5429
5430 if (cp_last != cp_this) {
5431 Refresh(false);
5432 b_ret = true;
5433 }
5434 }
5435 // Create the stack
5436 if (m_pCurrentStack) {
5437 assert(ChartData != 0);
5438 int current_db_index;
5439 current_db_index =
5440 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5441
5442 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5443 m_groupIndex);
5444 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5445 }
5446
5447 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5448 }
5449
5450 // Handle the quilted case
5451 if (VPoint.b_quilt) {
5452 if (last_vp.view_scale_ppm != scale_ppm)
5453 m_pQuilt->InvalidateAllQuiltPatchs();
5454
5455 // Create the quilt
5456 if (ChartData /*&& ChartData->IsValid()*/) {
5457 if (!m_pCurrentStack) return false;
5458
5459 int current_db_index;
5460 current_db_index =
5461 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5462
5463 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5464 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5465
5466 // Check to see if the current quilt reference chart is in the new stack
5467 int current_ref_stack_index = -1;
5468 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5469 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5470 current_ref_stack_index = i;
5471 }
5472
5473 if (g_bFullScreenQuilt) {
5474 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5475 }
5476
5477 // We might need a new Reference Chart
5478 bool b_needNewRef = false;
5479
5480 // If the new stack does not contain the current ref chart....
5481 if ((-1 == current_ref_stack_index) &&
5482 (m_pQuilt->GetRefChartdbIndex() >= 0))
5483 b_needNewRef = true;
5484
5485 // Would the current Ref Chart be excessively underzoomed?
5486 // We need to check this here to be sure, since we cannot know where the
5487 // reference chart was assigned. For instance, the reference chart may
5488 // have been selected from the config file, or from a long jump with a
5489 // chart family switch implicit. Anyway, we check to be sure....
5490 bool renderable = true;
5491 ChartBase *referenceChart =
5492 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5493 if (referenceChart) {
5494 double chartMaxScale = referenceChart->GetNormalScaleMax(
5495 GetCanvasScaleFactor(), GetCanvasWidth());
5496 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5497 }
5498 if (!renderable) b_needNewRef = true;
5499
5500 // Need new refchart?
5501 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5502 const ChartTableEntry &cte_ref =
5503 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5504 int target_scale = cte_ref.GetScale();
5505 int target_type = cte_ref.GetChartType();
5506 int candidate_stack_index;
5507
5508 // reset the ref chart in a way that does not lead to excessive
5509 // underzoom, for performance reasons Try to find a chart that is the
5510 // same type, and has a scale of just smaller than the current ref
5511 // chart
5512
5513 candidate_stack_index = 0;
5514 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5515 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5516 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5517 int candidate_scale = cte_candidate.GetScale();
5518 int candidate_type = cte_candidate.GetChartType();
5519
5520 if ((candidate_scale >= target_scale) &&
5521 (candidate_type == target_type)) {
5522 bool renderable = true;
5523 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5524 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5525 if (tentative_referenceChart) {
5526 double chartMaxScale =
5527 tentative_referenceChart->GetNormalScaleMax(
5528 GetCanvasScaleFactor(), GetCanvasWidth());
5529 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5530 }
5531
5532 if (renderable) break;
5533 }
5534
5535 candidate_stack_index++;
5536 }
5537
5538 // If that did not work, look for a chart of just larger scale and
5539 // same type
5540 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5541 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5542 while (candidate_stack_index >= 0) {
5543 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5544 if (idx >= 0) {
5545 const ChartTableEntry &cte_candidate =
5546 ChartData->GetChartTableEntry(idx);
5547 int candidate_scale = cte_candidate.GetScale();
5548 int candidate_type = cte_candidate.GetChartType();
5549
5550 if ((candidate_scale <= target_scale) &&
5551 (candidate_type == target_type))
5552 break;
5553 }
5554 candidate_stack_index--;
5555 }
5556 }
5557
5558 // and if that did not work, chose stack entry 0
5559 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5560 (candidate_stack_index < 0))
5561 candidate_stack_index = 0;
5562
5563 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5564
5565 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5566 }
5567
5568 if (!g_bopengl) {
5569 // Preset the VPoint projection type to match what the quilt projection
5570 // type will be
5571 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5572
5573 // Always keep the default Mercator projection if the reference chart is
5574 // not in the PatchList or the scale is too small for it to render.
5575
5576 bool renderable = true;
5577 ChartBase *referenceChart =
5578 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5579 if (referenceChart) {
5580 double chartMaxScale = referenceChart->GetNormalScaleMax(
5581 GetCanvasScaleFactor(), GetCanvasWidth());
5582 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5583 proj = ChartData->GetDBChartProj(ref_db_index);
5584 } else
5585 proj = PROJECTION_MERCATOR;
5586
5587 VPoint.b_MercatorProjectionOverride =
5588 (m_pQuilt->GetnCharts() == 0 || !renderable);
5589
5590 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5591
5592 VPoint.SetProjectionType(proj);
5593 }
5594
5595 VPoint.SetBoxes();
5596
5597 // If this quilt will be a perceptible delta from the existing quilt,
5598 // then refresh the entire screen
5599 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5600 // Allow the quilt to adjust the new ViewPort for performance
5601 // optimization This will normally be only a fractional (i.e.
5602 // sub-pixel) adjustment...
5603 if (b_adjust) {
5604 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5605 }
5606
5607 // ChartData->ClearCacheInUseFlags();
5608 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5609
5610 // wxStopWatch sw;
5611
5612#ifdef __ANDROID__
5613 // This is an optimization for panning on touch screen systems.
5614 // The quilt composition is deferred until the OnPaint() message gets
5615 // finally removed and processed from the message queue.
5616 // Takes advantage of the fact that touch-screen pan gestures are
5617 // usually short in distance,
5618 // so not requiring a full quilt rebuild until the pan gesture is
5619 // complete.
5620 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5621 // qDebug() << "Force compose";
5622 m_pQuilt->Compose(VPoint);
5623 } else {
5624 m_pQuilt->Invalidate();
5625 }
5626#else
5627 m_pQuilt->Compose(VPoint);
5628#endif
5629
5630 // printf("comp time %ld\n", sw.Time());
5631
5632 // If the extended chart stack has changed, invalidate any cached
5633 // render bitmap
5634 // if(m_pQuilt->GetXStackHash() != hash1) {
5635 // m_bm_cache_vp.Invalidate();
5636 // InvalidateGL();
5637 // }
5638
5639 ChartData->PurgeCacheUnusedCharts(0.7);
5640
5641 if (b_refresh) Refresh(false);
5642
5643 b_ret = true;
5644 }
5645 }
5646
5647 VPoint.skew = 0.; // Quilting supports 0 Skew
5648 } else if (!g_bopengl) {
5649 OcpnProjType projection = PROJECTION_UNKNOWN;
5650 if (m_singleChart) // viewport projection must match chart projection
5651 // without opengl
5652 projection = m_singleChart->GetChartProjectionType();
5653 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5654 VPoint.SetProjectionType(projection);
5655 }
5656
5657 // Has the Viewport projection changed? If so, invalidate the vp
5658 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5659 m_cache_vp.Invalidate();
5660 InvalidateGL();
5661 }
5662
5663 UpdateCanvasControlBar(); // Refresh the Piano
5664
5665 VPoint.chart_scale = 1.0; // fallback default value
5666
5667 /*if (!VPoint.GetBBox().GetValid())*/ VPoint.SetBoxes();
5668
5669 if (VPoint.GetBBox().GetValid()) {
5670 // Update the viewpoint reference scale
5671 if (m_singleChart)
5672 VPoint.ref_scale = m_singleChart->GetNativeScale();
5673 else {
5674#ifdef __ANDROID__
5675 // This is an optimization for panning on touch screen systems.
5676 // See above.
5677 // Quilt might not be fully composed at this point, so for cm93
5678 // the reference scale may not be known.
5679 // In this case, do not update the VP ref_scale.
5680 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5681 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5682 }
5683#else
5684 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5685#endif
5686 }
5687
5688 // Calculate the on-screen displayed actual scale
5689 // by a simple traverse northward from the center point
5690 // of roughly one eighth of the canvas height
5691 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5692
5693 double delta_check =
5694 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5695 delta_check /= 8.;
5696
5697 double check_point = wxMin(89., VPoint.clat);
5698
5699 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5700
5701 double rhumbDist;
5702 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5703 VPoint.clon, 0, &rhumbDist);
5704
5705 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5706 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5707 // Calculate the distance between r1 and r in physical pixels.
5708 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5709 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5710
5711 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5712
5713 // A fall back in case of very high zoom-out, giving delta_y == 0
5714 // which can probably only happen with vector charts
5715 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5716
5717 // Another fallback, for highly zoomed out charts
5718 // This adjustment makes the displayed TrueScale correspond to the
5719 // same algorithm used to calculate the chart zoom-out limit for
5720 // ChartDummy.
5721 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5722
5723 if (m_true_scale_ppm)
5724 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5725 else
5726 VPoint.chart_scale = 1.0;
5727
5728 // Create a nice renderable string
5729 double round_factor = 1000.;
5730 if (VPoint.chart_scale <= 1000.)
5731 round_factor = 10.;
5732 else if (VPoint.chart_scale <= 10000.)
5733 round_factor = 100.;
5734 else if (VPoint.chart_scale <= 100000.)
5735 round_factor = 1000.;
5736
5737 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5738 double retina_coef = 1;
5739#ifdef ocpnUSE_GL
5740#ifdef __WXOSX__
5741 if (g_bopengl) {
5742 retina_coef = GetContentScaleFactor();
5743 }
5744#endif
5745#endif
5746
5747 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5748 // rounded to the nearest 10, 100 or 1000.
5749 //
5750 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5751 // true_scale_display. That does not make sense. The chart scale should be
5752 // the same as the true scale within the limits of the rounding factor.
5753 double true_scale_display =
5754 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5755 wxString text;
5756
5757 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5758
5759 if (m_displayed_scale_factor > 10.0)
5760 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5761 m_displayed_scale_factor);
5762 else if (m_displayed_scale_factor > 1.0)
5763 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5764 m_displayed_scale_factor);
5765 else if (m_displayed_scale_factor > 0.1) {
5766 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5767 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5768 } else if (m_displayed_scale_factor > 0.01) {
5769 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5770 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5771 } else {
5772 text.Printf(
5773 "%s %4.0f (---)", _("Scale"),
5774 true_scale_display); // Generally, no chart, so no chart scale factor
5775 }
5776
5777 m_scaleValue = true_scale_display;
5778 m_scaleText = text;
5779 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5780
5781 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5782 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5783 // Check to see if the text will fit in the StatusBar field...
5784 bool b_noshow = false;
5785 {
5786 int w = 0;
5787 int h;
5788 wxClientDC dc(parent_frame->GetStatusBar());
5789 if (dc.IsOk()) {
5790 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5791 dc.SetFont(*templateFont);
5792 dc.GetTextExtent(text, &w, &h);
5793
5794 // If text is too long for the allocated field, try to reduce the text
5795 // string a bit.
5796 wxRect rect;
5797 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5798 if (w && w > rect.width) {
5799 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5800 }
5801
5802 // Test again...if too big still, then give it up.
5803 dc.GetTextExtent(text, &w, &h);
5804
5805 if (w && w > rect.width) {
5806 b_noshow = true;
5807 }
5808 }
5809 }
5810
5811 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5812 }
5813 }
5814
5815 // Maintain member vLat/vLon
5816 m_vLat = VPoint.clat;
5817 m_vLon = VPoint.clon;
5818
5819 return b_ret;
5820}
5821
5822// Static Icon definitions for some symbols requiring
5823// scaling/rotation/translation Very specific wxDC draw commands are
5824// necessary to properly render these icons...See the code in
5825// ShipDraw()
5826
5827// This icon was adapted and scaled from the S52 Presentation Library
5828// version 3_03.
5829// Symbol VECGND02
5830
5831static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5832
5833// This ownship icon was adapted and scaled from the S52 Presentation
5834// Library version 3_03 Symbol OWNSHP05
5835static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5836 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5837
5838wxColour ChartCanvas::PredColor() {
5839 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5840 // visibility.
5841 if (SHIP_NORMAL == m_ownship_state)
5842 return GetGlobalColor("URED");
5843
5844 else if (SHIP_LOWACCURACY == m_ownship_state)
5845 return GetGlobalColor("YELO1");
5846
5847 return GetGlobalColor("NODTA");
5848}
5849
5850wxColour ChartCanvas::ShipColor() {
5851 // Establish ship color
5852 // It changes color based on GPS and Chart accuracy/availability
5853
5854 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5855
5856 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5857
5858 return GetGlobalColor("URED"); // default is OK
5859}
5860
5861void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5862 wxPoint2DDouble lShipMidPoint) {
5863 dc.SetPen(wxPen(PredColor(), 2));
5864
5865 if (SHIP_NORMAL == m_ownship_state)
5866 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5867 else
5868 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5869
5870 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5871 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5872
5873 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5874 lShipMidPoint.m_y);
5875 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5876 lShipMidPoint.m_y + 12);
5877}
5878
5879void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5880 wxPoint GPSOffsetPixels,
5881 wxPoint2DDouble lGPSPoint) {
5882 // if (m_animationActive) return;
5883 // Develop a uniform length for course predictor line dash length, based on
5884 // physical display size Use this reference length to size all other graphics
5885 // elements
5886 float ref_dim = m_display_size_mm / 24;
5887 ref_dim = wxMin(ref_dim, 12);
5888 ref_dim = wxMax(ref_dim, 6);
5889
5890 wxColour cPred;
5891 cPred.Set(g_cog_predictor_color);
5892 if (cPred == wxNullColour) cPred = PredColor();
5893
5894 // Establish some graphic element line widths dependent on the platform
5895 // display resolution
5896 // double nominal_line_width_pix = wxMax(1.0,
5897 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5898 // not less than 1 pixel
5899 double nominal_line_width_pix = wxMax(
5900 1.0,
5901 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5902
5903 // If the calculated value is greater than the config file spec value, then
5904 // use it.
5905 if (nominal_line_width_pix > g_cog_predictor_width)
5906 g_cog_predictor_width = nominal_line_width_pix;
5907
5908 // Calculate ownship Position Predictor
5909 wxPoint lPredPoint, lHeadPoint;
5910
5911 float pCog = std::isnan(gCog) ? 0 : gCog;
5912 float pSog = std::isnan(gSog) ? 0 : gSog;
5913
5914 double pred_lat, pred_lon;
5915 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5916 &pred_lat, &pred_lon);
5917 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5918
5919 // test to catch the case where COG/HDG line crosses the screen
5920 LLBBox box;
5921
5922 // Should we draw the Head vector?
5923 // Compare the points lHeadPoint and lPredPoint
5924 // If they differ by more than n pixels, and the head vector is valid, then
5925 // render the head vector
5926
5927 float ndelta_pix = 10.;
5928 double hdg_pred_lat, hdg_pred_lon;
5929 bool b_render_hdt = false;
5930 if (!std::isnan(gHdt)) {
5931 // Calculate ownship Heading pointer as a predictor
5932 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5933 &hdg_pred_lon);
5934 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5935 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5936 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5937 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5938 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5939 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5940 }
5941 }
5942
5943 // draw course over ground if they are longer than the ship
5944 wxPoint lShipMidPoint;
5945 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5946 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5947 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5948 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5949
5950 if (lpp >= img_height / 2) {
5951 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5952 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5953 !std::isnan(gSog)) {
5954 // COG Predictor
5955 float dash_length = ref_dim;
5956 wxDash dash_long[2];
5957 dash_long[0] =
5958 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
5959 g_cog_predictor_width); // Long dash , in mm <---------+
5960 dash_long[1] = dash_long[0] / 2.0; // Short gap
5961
5962 // On ultra-hi-res displays, do not allow the dashes to be greater than
5963 // 250, since it is defined as (char)
5964 if (dash_length > 250.) {
5965 dash_long[0] = 250. / g_cog_predictor_width;
5966 dash_long[1] = dash_long[0] / 2;
5967 }
5968
5969 wxPen ppPen2(cPred, g_cog_predictor_width,
5970 (wxPenStyle)g_cog_predictor_style);
5971 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
5972 ppPen2.SetDashes(2, dash_long);
5973 dc.SetPen(ppPen2);
5974 dc.StrokeLine(
5975 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
5976 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
5977
5978 if (g_cog_predictor_width > 1) {
5979 float line_width = g_cog_predictor_width / 3.;
5980
5981 wxDash dash_long3[2];
5982 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
5983 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
5984
5985 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
5986 (wxPenStyle)g_cog_predictor_style);
5987 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
5988 ppPen3.SetDashes(2, dash_long3);
5989 dc.SetPen(ppPen3);
5990 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
5991 lGPSPoint.m_y + GPSOffsetPixels.y,
5992 lPredPoint.x + GPSOffsetPixels.x,
5993 lPredPoint.y + GPSOffsetPixels.y);
5994 }
5995
5996 if (g_cog_predictor_endmarker) {
5997 // Prepare COG predictor endpoint icon
5998 double png_pred_icon_scale_factor = .4;
5999 if (g_ShipScaleFactorExp > 1.0)
6000 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6001 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6002
6003 wxPoint icon[4];
6004
6005 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6006 (float)(lPredPoint.x - lShipMidPoint.x));
6007 cog_rad += (float)PI;
6008
6009 for (int i = 0; i < 4; i++) {
6010 int j = i * 2;
6011 double pxa = (double)(s_png_pred_icon[j]);
6012 double pya = (double)(s_png_pred_icon[j + 1]);
6013
6014 pya *= png_pred_icon_scale_factor;
6015 pxa *= png_pred_icon_scale_factor;
6016
6017 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6018 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6019
6020 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6021 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6022 }
6023
6024 // Render COG endpoint icon
6025 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6026 wxPENSTYLE_SOLID);
6027 dc.SetPen(ppPen1);
6028 dc.SetBrush(wxBrush(cPred));
6029
6030 dc.StrokePolygon(4, icon);
6031 }
6032 }
6033 }
6034
6035 // HDT Predictor
6036 if (b_render_hdt) {
6037 float hdt_dash_length = ref_dim * 0.4;
6038
6039 cPred.Set(g_ownship_HDTpredictor_color);
6040 if (cPred == wxNullColour) cPred = PredColor();
6041 float hdt_width =
6042 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6043 : g_cog_predictor_width * 0.8);
6044 wxDash dash_short[2];
6045 dash_short[0] =
6046 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6047 hdt_width); // Short dash , in mm <---------+
6048 dash_short[1] =
6049 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6050 hdt_width); // Short gap |
6051
6052 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6053 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6054 ppPen2.SetDashes(2, dash_short);
6055
6056 dc.SetPen(ppPen2);
6057 dc.StrokeLine(
6058 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6059 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6060
6061 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6062 dc.SetPen(ppPen1);
6063 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6064
6065 if (g_ownship_HDTpredictor_endmarker) {
6066 double nominal_circle_size_pixels = wxMax(
6067 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6068
6069 // Scale the circle to ChartScaleFactor, slightly softened....
6070 if (g_ShipScaleFactorExp > 1.0)
6071 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6072
6073 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6074 lHeadPoint.y + GPSOffsetPixels.y,
6075 nominal_circle_size_pixels / 2);
6076 }
6077 }
6078
6079 // Draw radar rings if activated
6080 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6081 double factor = 1.00;
6082 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6083 factor = 1 / 1.852;
6084 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6085 if (std::isnan(gSog))
6086 factor = 0.0;
6087 else
6088 factor = gSog / 60;
6089 }
6090 factor *= g_fNavAidRadarRingsStep;
6091
6092 double tlat, tlon;
6093 wxPoint r;
6094 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6095 GetCanvasPointPix(tlat, tlon, &r);
6096
6097 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6098 pow((double)(lGPSPoint.m_y - r.y), 2));
6099 int pix_radius = (int)lpp;
6100
6101 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6102
6103 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6104
6105 dc.SetPen(ppPen1);
6106 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6107
6108 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6109 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6110 }
6111}
6112
6113void ChartCanvas::ComputeShipScaleFactor(
6114 float icon_hdt, int ownShipWidth, int ownShipLength,
6115 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6116 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6117 float screenResolution = m_pix_per_mm;
6118
6119 // Calculate the true ship length in exact pixels
6120 double ship_bow_lat, ship_bow_lon;
6121 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6122 &ship_bow_lat, &ship_bow_lon);
6123 wxPoint lShipBowPoint;
6124 wxPoint2DDouble b_point =
6125 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6126 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6127
6128 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6129 powf((float)(b_point.m_y - a_point.m_y), 2));
6130
6131 // And in mm
6132 float shipLength_mm = shipLength_px / screenResolution;
6133
6134 // Set minimum ownship drawing size
6135 float ownship_min_mm = g_n_ownship_min_mm;
6136 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6137
6138 // Calculate Nautical Miles distance from midships to gps antenna
6139 float hdt_ant = icon_hdt + 180.;
6140 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6141 float dx = g_n_gps_antenna_offset_x / 1852.;
6142 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6143 {
6144 hdt_ant = icon_hdt;
6145 dy = -dy;
6146 }
6147
6148 // If the drawn ship size is going to be clamped, adjust the gps antenna
6149 // offsets
6150 if (shipLength_mm < ownship_min_mm) {
6151 dy /= shipLength_mm / ownship_min_mm;
6152 dx /= shipLength_mm / ownship_min_mm;
6153 }
6154
6155 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6156
6157 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6158 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6159 &ship_mid_lon1);
6160
6161 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6162 &lShipMidPoint);
6163
6164 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6165 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6166
6167 float scale_factor = shipLength_px / ownShipLength;
6168
6169 // Calculate a scale factor that would produce a reasonably sized icon
6170 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6171
6172 // And choose the correct one
6173 scale_factor = wxMax(scale_factor, scale_factor_min);
6174
6175 scale_factor_y = scale_factor;
6176 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6177 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6178}
6179
6180void ChartCanvas::ShipDraw(ocpnDC &dc) {
6181 if (!GetVP().IsValid()) return;
6182
6183 wxPoint GPSOffsetPixels(0, 0);
6184 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6185
6186 // COG/SOG may be undefined in NMEA data stream
6187 float pCog = std::isnan(gCog) ? 0 : gCog;
6188 float pSog = std::isnan(gSog) ? 0 : gSog;
6189
6190 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6191
6192 lShipMidPoint = lGPSPoint;
6193
6194 // Draw the icon rotated to the COG
6195 // or to the Hdt if available
6196 float icon_hdt = pCog;
6197 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6198
6199 // COG may be undefined in NMEA data stream
6200 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6201
6202 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6203 // predictor
6204 double osd_head_lat, osd_head_lon;
6205 wxPoint osd_head_point;
6206
6207 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6208 &osd_head_lon);
6209
6210 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6211
6212 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6213 (float)(osd_head_point.x - lShipMidPoint.m_x));
6214 icon_rad += (float)PI;
6215
6216 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6217
6218 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6219 // nominal size and is just barely outside the viewport ....
6220 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6221
6222 // TODO: fix to include actual size of boat that will be rendered
6223 int img_height = 0;
6224 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6225 if (GetVP().chart_scale >
6226 300000) // According to S52, this should be 50,000
6227 {
6228 ShipDrawLargeScale(dc, lShipMidPoint);
6229 img_height = 20;
6230 } else {
6231 wxImage pos_image;
6232
6233 // Substitute user ownship image if found
6234 if (m_pos_image_user)
6235 pos_image = m_pos_image_user->Copy();
6236 else if (SHIP_NORMAL == m_ownship_state)
6237 pos_image = m_pos_image_red->Copy();
6238 if (SHIP_LOWACCURACY == m_ownship_state)
6239 pos_image = m_pos_image_yellow->Copy();
6240 else if (SHIP_NORMAL != m_ownship_state)
6241 pos_image = m_pos_image_grey->Copy();
6242
6243 // Substitute user ownship image if found
6244 if (m_pos_image_user) {
6245 pos_image = m_pos_image_user->Copy();
6246
6247 if (SHIP_LOWACCURACY == m_ownship_state)
6248 pos_image = m_pos_image_user_yellow->Copy();
6249 else if (SHIP_NORMAL != m_ownship_state)
6250 pos_image = m_pos_image_user_grey->Copy();
6251 }
6252
6253 img_height = pos_image.GetHeight();
6254
6255 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6256 g_OwnShipIconType > 0) // use large ship
6257 {
6258 int ownShipWidth = 22; // Default values from s_ownship_icon
6259 int ownShipLength = 84;
6260 if (g_OwnShipIconType == 1) {
6261 ownShipWidth = pos_image.GetWidth();
6262 ownShipLength = pos_image.GetHeight();
6263 }
6264
6265 float scale_factor_x, scale_factor_y;
6266 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6267 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6268 scale_factor_x, scale_factor_y);
6269
6270 if (g_OwnShipIconType == 1) { // Scaled bitmap
6271 pos_image.Rescale(ownShipWidth * scale_factor_x,
6272 ownShipLength * scale_factor_y,
6273 wxIMAGE_QUALITY_HIGH);
6274 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6275 wxImage rot_image =
6276 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6277
6278 // Simple sharpening algorithm.....
6279 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6280 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6281 if (rot_image.GetAlpha(ip, jp) > 64)
6282 rot_image.SetAlpha(ip, jp, 255);
6283
6284 wxBitmap os_bm(rot_image);
6285
6286 int w = os_bm.GetWidth();
6287 int h = os_bm.GetHeight();
6288 img_height = h;
6289
6290 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6291 lShipMidPoint.m_y - h / 2, true);
6292
6293 // Maintain dirty box,, missing in __WXMSW__ library
6294 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6295 lShipMidPoint.m_y - h / 2);
6296 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6297 lShipMidPoint.m_y - h / 2 + h);
6298 }
6299
6300 else if (g_OwnShipIconType == 2) { // Scaled Vector
6301 wxPoint ownship_icon[10];
6302
6303 for (int i = 0; i < 10; i++) {
6304 int j = i * 2;
6305 float pxa = (float)(s_ownship_icon[j]);
6306 float pya = (float)(s_ownship_icon[j + 1]);
6307 pya *= scale_factor_y;
6308 pxa *= scale_factor_x;
6309
6310 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6311 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6312
6313 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6314 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6315 }
6316
6317 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6318 dc.SetPen(ppPen1);
6319 dc.SetBrush(wxBrush(ShipColor()));
6320
6321 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6322
6323 // draw reference point (midships) cross
6324 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6325 ownship_icon[7].y);
6326 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6327 ownship_icon[9].y);
6328 }
6329
6330 img_height = ownShipLength * scale_factor_y;
6331
6332 // Reference point, where the GPS antenna is
6333 int circle_rad = 3;
6334 if (m_pos_image_user) circle_rad = 1;
6335
6336 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6337 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6338 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6339 } else { // Fixed bitmap icon.
6340 /* non opengl, or suboptimal opengl via ocpndc: */
6341 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6342 wxImage rot_image =
6343 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6344
6345 // Simple sharpening algorithm.....
6346 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6347 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6348 if (rot_image.GetAlpha(ip, jp) > 64)
6349 rot_image.SetAlpha(ip, jp, 255);
6350
6351 wxBitmap os_bm(rot_image);
6352
6353 if (g_ShipScaleFactorExp > 1) {
6354 wxImage scaled_image = os_bm.ConvertToImage();
6355 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6356 1.0; // soften the scale factor a bit
6357 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6358 scaled_image.GetHeight() * factor,
6359 wxIMAGE_QUALITY_HIGH));
6360 }
6361 int w = os_bm.GetWidth();
6362 int h = os_bm.GetHeight();
6363 img_height = h;
6364
6365 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6366 lShipMidPoint.m_y - h / 2, true);
6367
6368 // Reference point, where the GPS antenna is
6369 int circle_rad = 3;
6370 if (m_pos_image_user) circle_rad = 1;
6371
6372 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6373 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6374 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6375
6376 // Maintain dirty box,, missing in __WXMSW__ library
6377 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6378 lShipMidPoint.m_y - h / 2);
6379 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6380 lShipMidPoint.m_y - h / 2 + h);
6381 }
6382 } // ownship draw
6383 }
6384
6385 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6386}
6387
6388/* @ChartCanvas::CalcGridSpacing
6389 **
6390 ** Calculate the major and minor spacing between the lat/lon grid
6391 **
6392 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6393 *window
6394 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6395 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6396 ** @return [void]
6397 */
6398void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6399 float &MinorSpacing) {
6400 // table for calculating the distance between the grids
6401 // [0] view_scale ppm
6402 // [1] spacing between major grid lines in degrees
6403 // [2] spacing between minor grid lines in degrees
6404 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6405 {.000001f, 45.0f, 15.0f},
6406 {.0002f, 30.0f, 10.0f},
6407 {.0003f, 10.0f, 2.0f},
6408 {.0008f, 5.0f, 1.0f},
6409 {.001f, 2.0f, 30.0f / 60.0f},
6410 {.003f, 1.0f, 20.0f / 60.0f},
6411 {.006f, 0.5f, 10.0f / 60.0f},
6412 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6413 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6414 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6415 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6416 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6417 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6418 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6419 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6420
6421 unsigned int tabi;
6422 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6423 if (view_scale_ppm < lltab[tabi][0]) break;
6424 MajorSpacing = lltab[tabi][1]; // major latitude distance
6425 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6426 return;
6427}
6428/* @ChartCanvas::CalcGridText *************************************
6429 **
6430 ** Calculates text to display at the major grid lines
6431 **
6432 ** @param [r] latlon [float] latitude or longitude of grid line
6433 ** @param [r] spacing [float] distance between two major grid lines
6434 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6435 **
6436 ** @return
6437 */
6438
6439wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6440 int deg = (int)fabs(latlon); // degrees
6441 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6442 char postfix;
6443
6444 // calculate postfix letter (NSEW)
6445 if (latlon > 0.0) {
6446 if (bPostfix) {
6447 postfix = 'N';
6448 } else {
6449 postfix = 'E';
6450 }
6451 } else if (latlon < 0.0) {
6452 if (bPostfix) {
6453 postfix = 'S';
6454 } else {
6455 postfix = 'W';
6456 }
6457 } else {
6458 postfix = ' '; // no postfix for equator and greenwich
6459 }
6460 // calculate text, display minutes only if spacing is smaller than one degree
6461
6462 wxString ret;
6463 if (spacing >= 1.0) {
6464 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6465 } else if (spacing >= (1.0 / 60.0)) {
6466 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6467 } else {
6468 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6469 }
6470
6471 return ret;
6472}
6473
6474/* @ChartCanvas::GridDraw *****************************************
6475 **
6476 ** Draws major and minor Lat/Lon Grid on the chart
6477 ** - distance between Grid-lm ines are calculated automatic
6478 ** - major grid lines will be across the whole chart window
6479 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6480 **
6481 ** @param [w] dc [wxDC&] the wx drawing context
6482 **
6483 ** @return [void]
6484 ************************************************************************/
6485void ChartCanvas::GridDraw(ocpnDC &dc) {
6486 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6487
6488 double nlat, elon, slat, wlon;
6489 float lat, lon;
6490 float dlon;
6491 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6492 wxCoord w, h;
6493 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6494 dc.SetPen(GridPen);
6495 if (!m_pgridFont) SetupGridFont();
6496 dc.SetFont(*m_pgridFont);
6497 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6498
6499 w = m_canvas_width;
6500 h = m_canvas_height;
6501
6502 GetCanvasPixPoint(0, 0, nlat,
6503 wlon); // get lat/lon of upper left point of the window
6504 GetCanvasPixPoint(w, h, slat,
6505 elon); // get lat/lon of lower right point of the window
6506 dlon =
6507 elon -
6508 wlon; // calculate how many degrees of longitude are shown in the window
6509 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6510 {
6511 dlon = dlon + 360.0;
6512 }
6513 // calculate distance between latitude grid lines
6514 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6515
6516 // calculate position of first major latitude grid line
6517 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6518
6519 // Draw Major latitude grid lines and text
6520 while (lat < nlat) {
6521 wxPoint r;
6522 wxString st =
6523 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6524 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6525 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6526 dc.DrawText(st, 0, r.y); // draw text
6527 lat = lat + gridlatMajor;
6528
6529 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6530 }
6531
6532 // calculate position of first minor latitude grid line
6533 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6534
6535 // Draw minor latitude grid lines
6536 while (lat < nlat) {
6537 wxPoint r;
6538 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6539 dc.DrawLine(0, r.y, 10, r.y, false);
6540 dc.DrawLine(w - 10, r.y, w, r.y, false);
6541 lat = lat + gridlatMinor;
6542 }
6543
6544 // calculate distance between grid lines
6545 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6546
6547 // calculate position of first major latitude grid line
6548 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6549
6550 // draw major longitude grid lines
6551 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6552 wxPoint r;
6553 wxString st = CalcGridText(lon, gridlonMajor, false);
6554 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6555 dc.DrawLine(r.x, 0, r.x, h, false);
6556 dc.DrawText(st, r.x, 0);
6557 lon = lon + gridlonMajor;
6558 if (lon > 180.0) {
6559 lon = lon - 360.0;
6560 }
6561
6562 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6563 }
6564
6565 // calculate position of first minor longitude grid line
6566 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6567 // draw minor longitude grid lines
6568 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6569 wxPoint r;
6570 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6571 dc.DrawLine(r.x, 0, r.x, 10, false);
6572 dc.DrawLine(r.x, h - 10, r.x, h, false);
6573 lon = lon + gridlonMinor;
6574 if (lon > 180.0) {
6575 lon = lon - 360.0;
6576 }
6577 }
6578}
6579
6580void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6581 if (0 ) {
6582 double blat, blon, tlat, tlon;
6583 wxPoint r;
6584
6585 int x_origin = m_bDisplayGrid ? 60 : 20;
6586 int y_origin = m_canvas_height - 50;
6587
6588 float dist;
6589 int count;
6590 wxPen pen1, pen2;
6591
6592 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6593 {
6594 dist = 10.0;
6595 count = 5;
6596 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6597 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6598 } else // Draw 1 mile scale as SCALEB10
6599 {
6600 dist = 1.0;
6601 count = 10;
6602 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6603 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6604 }
6605
6606 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6607 double rotation = -VPoint.rotation;
6608
6609 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6610 GetCanvasPointPix(tlat, tlon, &r);
6611 int l1 = (y_origin - r.y) / count;
6612
6613 for (int i = 0; i < count; i++) {
6614 int y = l1 * i;
6615 if (i & 1)
6616 dc.SetPen(pen1);
6617 else
6618 dc.SetPen(pen2);
6619
6620 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6621 }
6622 } else {
6623 double blat, blon, tlat, tlon;
6624
6625 int x_origin = 5.0 * GetPixPerMM();
6626 int chartbar_height = GetChartbarHeight();
6627 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6628 // if (style->chartStatusWindowTransparent)
6629 // chartbar_height = 0;
6630 int y_origin = m_canvas_height - chartbar_height - 5;
6631#ifdef __WXOSX__
6632 if (!g_bopengl)
6633 y_origin =
6634 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6635#endif
6636
6637 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6638 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6639
6640 double d;
6641 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6642 d /= 2;
6643
6644 int unit = g_iDistanceFormat;
6645 if (d < .5 &&
6646 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6647 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6648
6649 // nice number
6650 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6651 float places = floor(logdist), rem = logdist - places;
6652 dist = pow(10, places);
6653
6654 if (rem < .2)
6655 dist /= 5;
6656 else if (rem < .5)
6657 dist /= 2;
6658
6659 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6660 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6661 double rotation = -VPoint.rotation;
6662
6663 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6664 &tlat, &tlon);
6665 wxPoint r;
6666 GetCanvasPointPix(tlat, tlon, &r);
6667 int l1 = r.x - x_origin;
6668
6669 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6670 12); // Store this for later reference
6671
6672 dc.SetPen(pen1);
6673
6674 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6675 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6676 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6677
6678 if (!m_pgridFont) SetupGridFont();
6679 dc.SetFont(*m_pgridFont);
6680 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6681 int w, h;
6682 dc.GetTextExtent(s, &w, &h);
6683 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6684 if (g_bopengl) {
6685 w /= dpi_factor;
6686 h /= dpi_factor;
6687 }
6688 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6689 }
6690}
6691
6692void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6693 // Constants?
6694 double da_min = 2.;
6695 double da_max = 6.;
6696 double ra_min = 0.;
6697 double ra_max = 40.;
6698
6699 wxPen pen_save = dc.GetPen();
6700
6701 wxDateTime now = wxDateTime::Now();
6702
6703 dc.SetPen(pen);
6704
6705 int x0, y0, x1, y1;
6706
6707 x0 = x1 = x + radius; // Start point
6708 y0 = y1 = y;
6709 double angle = 0.;
6710 int i = 0;
6711
6712 while (angle < 360.) {
6713 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6714 angle += da;
6715
6716 if (angle > 360.) angle = 360.;
6717
6718 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6719
6720 double r;
6721 if (i & 1)
6722 r = radius + ra;
6723 else
6724 r = radius - ra;
6725
6726 x1 = (int)(x + cos(angle * PI / 180.) * r);
6727 y1 = (int)(y + sin(angle * PI / 180.) * r);
6728
6729 dc.DrawLine(x0, y0, x1, y1);
6730
6731 x0 = x1;
6732 y0 = y1;
6733
6734 i++;
6735 }
6736
6737 dc.DrawLine(x + radius, y, x1, y1); // closure
6738
6739 dc.SetPen(pen_save);
6740}
6741
6742static bool bAnchorSoundPlaying = false;
6743
6744static void onAnchorSoundFinished(void *ptr) {
6745 o_sound::g_anchorwatch_sound->UnLoad();
6746 bAnchorSoundPlaying = false;
6747}
6748
6749void ChartCanvas::AlertDraw(ocpnDC &dc) {
6750 using namespace o_sound;
6751 // Visual and audio alert for anchorwatch goes here
6752 bool play_sound = false;
6753 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6754 if (AnchorAlertOn1) {
6755 wxPoint TargetPoint;
6757 &TargetPoint);
6758 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6759 TargetPoint.y, 100);
6760 play_sound = true;
6761 }
6762 } else
6763 AnchorAlertOn1 = false;
6764
6765 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6766 if (AnchorAlertOn2) {
6767 wxPoint TargetPoint;
6769 &TargetPoint);
6770 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6771 TargetPoint.y, 100);
6772 play_sound = true;
6773 }
6774 } else
6775 AnchorAlertOn2 = false;
6776
6777 if (play_sound) {
6778 if (!bAnchorSoundPlaying) {
6779 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6780 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6781 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6782 if (g_anchorwatch_sound->IsOk()) {
6783 bAnchorSoundPlaying = true;
6784 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6785 g_anchorwatch_sound->Play();
6786 }
6787 }
6788 }
6789}
6790
6791void ChartCanvas::UpdateShips() {
6792 // Get the rectangle in the current dc which bounds the "ownship" symbol
6793
6794 wxClientDC dc(this);
6795 if (!dc.IsOk()) return;
6796
6797 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6798 if (!test_bitmap.IsOk()) return;
6799
6800 wxMemoryDC temp_dc(test_bitmap);
6801
6802 temp_dc.ResetBoundingBox();
6803 temp_dc.DestroyClippingRegion();
6804 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6805
6806 // Draw the ownship on the temp_dc
6807 ocpnDC ocpndc = ocpnDC(temp_dc);
6808 ShipDraw(ocpndc);
6809
6810 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6811 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6812 if (p) {
6813 wxPoint px;
6814 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6815 ocpndc.CalcBoundingBox(px.x, px.y);
6816 }
6817 }
6818
6819 ship_draw_rect =
6820 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6821 temp_dc.MaxY() - temp_dc.MinY());
6822
6823 wxRect own_ship_update_rect = ship_draw_rect;
6824
6825 if (!own_ship_update_rect.IsEmpty()) {
6826 // The required invalidate rectangle is the union of the last drawn
6827 // rectangle and this drawn rectangle
6828 own_ship_update_rect.Union(ship_draw_last_rect);
6829 own_ship_update_rect.Inflate(2);
6830 }
6831
6832 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6833
6834 ship_draw_last_rect = ship_draw_rect;
6835
6836 temp_dc.SelectObject(wxNullBitmap);
6837}
6838
6839void ChartCanvas::UpdateAlerts() {
6840 // Get the rectangle in the current dc which bounds the detected Alert
6841 // targets
6842
6843 // Use this dc
6844 wxClientDC dc(this);
6845
6846 // Get dc boundary
6847 int sx, sy;
6848 dc.GetSize(&sx, &sy);
6849
6850 // Need a bitmap
6851 wxBitmap test_bitmap(sx, sy, -1);
6852
6853 // Create a memory DC
6854 wxMemoryDC temp_dc;
6855 temp_dc.SelectObject(test_bitmap);
6856
6857 temp_dc.ResetBoundingBox();
6858 temp_dc.DestroyClippingRegion();
6859 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6860
6861 // Draw the Alert Targets on the temp_dc
6862 ocpnDC ocpndc = ocpnDC(temp_dc);
6863 AlertDraw(ocpndc);
6864
6865 // Retrieve the drawing extents
6866 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6867 temp_dc.MaxX() - temp_dc.MinX(),
6868 temp_dc.MaxY() - temp_dc.MinY());
6869
6870 if (!alert_rect.IsEmpty())
6871 alert_rect.Inflate(2); // clear all drawing artifacts
6872
6873 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6874 // The required invalidate rectangle is the union of the last drawn
6875 // rectangle and this drawn rectangle
6876 wxRect alert_update_rect = alert_draw_rect;
6877 alert_update_rect.Union(alert_rect);
6878
6879 // Invalidate the rectangular region
6880 RefreshRect(alert_update_rect, false);
6881 }
6882
6883 // Save this rectangle for next time
6884 alert_draw_rect = alert_rect;
6885
6886 temp_dc.SelectObject(wxNullBitmap); // clean up
6887}
6888
6889void ChartCanvas::UpdateAIS() {
6890 if (!g_pAIS) return;
6891
6892 // Get the rectangle in the current dc which bounds the detected AIS targets
6893
6894 // Use this dc
6895 wxClientDC dc(this);
6896
6897 // Get dc boundary
6898 int sx, sy;
6899 dc.GetSize(&sx, &sy);
6900
6901 wxRect ais_rect;
6902
6903 // How many targets are there?
6904
6905 // If more than "some number", it will be cheaper to refresh the entire
6906 // screen than to build update rectangles for each target.
6907 if (g_pAIS->GetTargetList().size() > 10) {
6908 ais_rect = wxRect(0, 0, sx, sy); // full screen
6909 } else {
6910 // Need a bitmap
6911 wxBitmap test_bitmap(sx, sy, -1);
6912
6913 // Create a memory DC
6914 wxMemoryDC temp_dc;
6915 temp_dc.SelectObject(test_bitmap);
6916
6917 temp_dc.ResetBoundingBox();
6918 temp_dc.DestroyClippingRegion();
6919 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6920
6921 // Draw the AIS Targets on the temp_dc
6922 ocpnDC ocpndc = ocpnDC(temp_dc);
6923 AISDraw(ocpndc, GetVP(), this);
6924 AISDrawAreaNotices(ocpndc, GetVP(), this);
6925
6926 // Retrieve the drawing extents
6927 ais_rect =
6928 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6929 temp_dc.MaxY() - temp_dc.MinY());
6930
6931 if (!ais_rect.IsEmpty())
6932 ais_rect.Inflate(2); // clear all drawing artifacts
6933
6934 temp_dc.SelectObject(wxNullBitmap); // clean up
6935 }
6936
6937 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6938 // The required invalidate rectangle is the union of the last drawn
6939 // rectangle and this drawn rectangle
6940 wxRect ais_update_rect = ais_draw_rect;
6941 ais_update_rect.Union(ais_rect);
6942
6943 // Invalidate the rectangular region
6944 RefreshRect(ais_update_rect, false);
6945 }
6946
6947 // Save this rectangle for next time
6948 ais_draw_rect = ais_rect;
6949}
6950
6951void ChartCanvas::ToggleCPAWarn() {
6952 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6953 wxString mess;
6954 if (g_bCPAWarn) {
6955 g_bTCPA_Max = true;
6956 mess = _("ON");
6957 } else {
6958 g_bTCPA_Max = false;
6959 mess = _("OFF");
6960 }
6961 // Print to status bar if available.
6962 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
6963 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
6964 } else {
6965 if (!g_AisFirstTimeUse) {
6966 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
6967 _("CPA") + " " + mess, 4, 4);
6968 }
6969 }
6970}
6971
6972void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
6973
6974void ChartCanvas::OnSize(wxSizeEvent &event) {
6975 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
6976 // GetClientSize returns the size of the canvas area in logical pixels.
6977 GetClientSize(&m_canvas_width, &m_canvas_height);
6978
6979#ifdef __WXOSX__
6980 // Support scaled HDPI displays.
6981 m_displayScale = GetContentScaleFactor();
6982#endif
6983
6984 // Convert to physical pixels.
6985 m_canvas_width *= m_displayScale;
6986 m_canvas_height *= m_displayScale;
6987
6988 // Resize the current viewport
6989 VPoint.pix_width = m_canvas_width;
6990 VPoint.pix_height = m_canvas_height;
6991 VPoint.SetPixelScale(m_displayScale);
6992
6993 // Get some canvas metrics
6994
6995 // Rescale to current value, in order to rebuild VPoint data
6996 // structures for new canvas size
6998
6999 m_absolute_min_scale_ppm =
7000 m_canvas_width /
7001 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7002
7003 // Inform the parent Frame that I am being resized...
7004 gFrame->ProcessCanvasResize();
7005
7006 // if MUIBar is active, size the bar
7007 // if(g_useMUI && !m_muiBar){ // rebuild if
7008 // necessary
7009 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7010 // m_muiBarHOSize = m_muiBar->GetSize();
7011 // }
7012
7013 if (m_muiBar) {
7014 SetMUIBarPosition();
7015 UpdateFollowButtonState();
7016 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7017 }
7018
7019 // Set up the scroll margins
7020 xr_margin = m_canvas_width * 95 / 100;
7021 xl_margin = m_canvas_width * 5 / 100;
7022 yt_margin = m_canvas_height * 5 / 100;
7023 yb_margin = m_canvas_height * 95 / 100;
7024
7025 if (m_pQuilt)
7026 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7027
7028 // Resize the scratch BM
7029 delete pscratch_bm;
7030 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7031 m_brepaint_piano = true;
7032
7033 // Resize the Route Calculation BM
7034 m_dc_route.SelectObject(wxNullBitmap);
7035 delete proute_bm;
7036 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7037 m_dc_route.SelectObject(*proute_bm);
7038
7039 // Resize the saved Bitmap
7040 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7041
7042 // Resize the working Bitmap
7043 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7044
7045 // Rescale again, to capture all the changes for new canvas size
7047
7048#ifdef ocpnUSE_GL
7049 if (/*g_bopengl &&*/ m_glcc) {
7050 // FIXME (dave) This can go away?
7051 m_glcc->OnSize(event);
7052 }
7053#endif
7054
7055 FormatPianoKeys();
7056 // Invalidate the whole window
7057 ReloadVP();
7058}
7059
7060void ChartCanvas::ProcessNewGUIScale() {
7061 // m_muiBar->Hide();
7062 delete m_muiBar;
7063 m_muiBar = 0;
7064
7065 CreateMUIBar();
7066}
7067
7068void ChartCanvas::CreateMUIBar() {
7069 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7070 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7071 m_muiBar->SetColorScheme(m_cs);
7072 m_muiBarHOSize = m_muiBar->m_size;
7073 }
7074
7075 if (m_muiBar) {
7076 // We need to update the m_bENCGroup flag, not least for the initial
7077 // creation of a MUIBar
7078 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7079
7080 SetMUIBarPosition();
7081 UpdateFollowButtonState();
7082 m_muiBar->UpdateDynamicValues();
7083 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7084 }
7085}
7086
7087void ChartCanvas::SetMUIBarPosition() {
7088 // if MUIBar is active, size the bar
7089 if (m_muiBar) {
7090 // We estimate the piano width based on the canvas width
7091 int pianoWidth = GetClientSize().x * 0.6f;
7092 // If the piano already exists, we can use its exact width
7093 // if(m_Piano)
7094 // pianoWidth = m_Piano->GetWidth();
7095
7096 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7097 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7098 delete m_muiBar;
7099 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7100 m_muiBar->SetColorScheme(m_cs);
7101 }
7102 }
7103
7104 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7105 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7106 delete m_muiBar;
7107 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7108 m_muiBar->SetColorScheme(m_cs);
7109 }
7110 }
7111
7112 m_muiBar->SetBestPosition();
7113 }
7114}
7115
7116void ChartCanvas::DestroyMuiBar() {
7117 if (m_muiBar) {
7118 delete m_muiBar;
7119 m_muiBar = NULL;
7120 }
7121}
7122
7123void ChartCanvas::ShowCompositeInfoWindow(
7124 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7125 if (n_charts > 0) {
7126 if (NULL == m_pCIWin) {
7127 m_pCIWin = new ChInfoWin(this);
7128 m_pCIWin->Hide();
7129 }
7130
7131 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7132 wxString s;
7133
7134 s = _("Composite of ");
7135
7136 wxString s1;
7137 s1.Printf("%d ", n_charts);
7138 if (n_charts > 1)
7139 s1 += _("charts");
7140 else
7141 s1 += _("chart");
7142 s += s1;
7143 s += '\n';
7144
7145 s1.Printf(_("Chart scale"));
7146 s1 += ": ";
7147 wxString s2;
7148 s2.Printf("1:%d\n", scale);
7149 s += s1;
7150 s += s2;
7151
7152 s1 = _("Zoom in for more information");
7153 s += s1;
7154 s += '\n';
7155
7156 int char_width = s1.Length();
7157 int char_height = 3;
7158
7159 if (g_bChartBarEx) {
7160 s += '\n';
7161 int j = 0;
7162 for (int i : index_vector) {
7163 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7164 wxString path = cte.GetFullSystemPath();
7165 s += path;
7166 s += '\n';
7167 char_height++;
7168 char_width = wxMax(char_width, path.Length());
7169 if (j++ >= 9) break;
7170 }
7171 if (j >= 9) {
7172 s += " .\n .\n .\n";
7173 char_height += 3;
7174 }
7175 s += '\n';
7176 char_height += 1;
7177
7178 char_width += 4; // Fluff
7179 }
7180
7181 m_pCIWin->SetString(s);
7182
7183 m_pCIWin->FitToChars(char_width, char_height);
7184
7185 wxPoint p;
7186 p.x = x / GetContentScaleFactor();
7187 if ((p.x + m_pCIWin->GetWinSize().x) >
7188 (m_canvas_width / GetContentScaleFactor()))
7189 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7190 m_pCIWin->GetWinSize().x) /
7191 2; // centered
7192
7193 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7194 4 - m_pCIWin->GetWinSize().y;
7195
7196 m_pCIWin->dbIndex = 0;
7197 m_pCIWin->chart_scale = 0;
7198 m_pCIWin->SetPosition(p);
7199 m_pCIWin->SetBitmap();
7200 m_pCIWin->Refresh();
7201 m_pCIWin->Show();
7202 }
7203 } else {
7204 HideChartInfoWindow();
7205 }
7206}
7207
7208void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7209 if (dbIndex >= 0) {
7210 if (NULL == m_pCIWin) {
7211 m_pCIWin = new ChInfoWin(this);
7212 m_pCIWin->Hide();
7213 }
7214
7215 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7216 wxString s;
7217 ChartBase *pc = NULL;
7218
7219 // TOCTOU race but worst case will reload chart.
7220 // need to lock it or the background spooler may evict charts in
7221 // OpenChartFromDBAndLock
7222 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7223 pc = ChartData->OpenChartFromDBAndLock(
7224 dbIndex, FULL_INIT); // this must come from cache
7225
7226 int char_width, char_height;
7227 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7228 if (pc) ChartData->UnLockCacheChart(dbIndex);
7229
7230 m_pCIWin->SetString(s);
7231 m_pCIWin->FitToChars(char_width, char_height);
7232
7233 wxPoint p;
7234 p.x = x / GetContentScaleFactor();
7235 if ((p.x + m_pCIWin->GetWinSize().x) >
7236 (m_canvas_width / GetContentScaleFactor()))
7237 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7238 m_pCIWin->GetWinSize().x) /
7239 2; // centered
7240
7241 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7242 4 - m_pCIWin->GetWinSize().y;
7243
7244 m_pCIWin->dbIndex = dbIndex;
7245 m_pCIWin->SetPosition(p);
7246 m_pCIWin->SetBitmap();
7247 m_pCIWin->Refresh();
7248 m_pCIWin->Show();
7249 }
7250 } else {
7251 HideChartInfoWindow();
7252 }
7253}
7254
7255void ChartCanvas::HideChartInfoWindow() {
7256 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7257 m_pCIWin->Hide();
7258 m_pCIWin->Destroy();
7259 m_pCIWin = NULL;
7260
7261#ifdef __ANDROID__
7262 androidForceFullRepaint();
7263#endif
7264 }
7265}
7266
7267void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7268 wxMouseEvent ev(wxEVT_MOTION);
7269 ev.m_x = mouse_x;
7270 ev.m_y = mouse_y;
7271 ev.m_leftDown = mouse_leftisdown;
7272
7273 wxEvtHandler *evthp = GetEventHandler();
7274
7275 ::wxPostEvent(evthp, ev);
7276}
7277
7278void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7279 if ((m_panx_target_final - m_panx_target_now) ||
7280 (m_pany_target_final - m_pany_target_now)) {
7281 DoTimedMovementTarget();
7282 } else
7283 DoTimedMovement();
7284}
7285
7286void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7287
7288bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7289 int delta) {
7290 if (m_disable_edge_pan) return false;
7291
7292 bool bft = false;
7293 int pan_margin = m_canvas_width * margin / 100;
7294 int pan_timer_set = 200;
7295 double pan_delta = GetVP().pix_width * delta / 100;
7296 int pan_x = 0;
7297 int pan_y = 0;
7298
7299 if (x > m_canvas_width - pan_margin) {
7300 bft = true;
7301 pan_x = pan_delta;
7302 }
7303
7304 else if (x < pan_margin) {
7305 bft = true;
7306 pan_x = -pan_delta;
7307 }
7308
7309 if (y < pan_margin) {
7310 bft = true;
7311 pan_y = -pan_delta;
7312 }
7313
7314 else if (y > m_canvas_height - pan_margin) {
7315 bft = true;
7316 pan_y = pan_delta;
7317 }
7318
7319 // Of course, if dragging, and the mouse left button is not down, we must
7320 // stop the event injection
7321 if (bdragging) {
7322 if (!g_btouch) {
7323 wxMouseState state = ::wxGetMouseState();
7324#if wxCHECK_VERSION(3, 0, 0)
7325 if (!state.LeftIsDown())
7326#else
7327 if (!state.LeftDown())
7328#endif
7329 bft = false;
7330 }
7331 }
7332 if ((bft) && !pPanTimer->IsRunning()) {
7333 PanCanvas(pan_x, pan_y);
7334 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7335 return true;
7336 }
7337
7338 // This mouse event must not be due to pan timer event injector
7339 // Mouse is out of the pan zone, so prevent any orphan event injection
7340 if ((!bft) && pPanTimer->IsRunning()) {
7341 pPanTimer->Stop();
7342 }
7343
7344 return (false);
7345}
7346
7347// Look for waypoints at the current position.
7348// Used to determine what a mouse event should act on.
7349
7350void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7351 bool setBeingEdited) {
7352 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7353 m_pRoutePointEditTarget = NULL;
7354 m_pFoundPoint = NULL;
7355
7356 SelectItem *pFind = NULL;
7357 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7358 SelectableItemList SelList = pSelect->FindSelectionList(
7359 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7360 for (SelectItem *pFind : SelList) {
7361 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7362
7363 // Get an array of all routes using this point
7364 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7365 // TODO: delete m_pEditRouteArray after use?
7366
7367 // Use route array to determine actual visibility for the point
7368 bool brp_viz = false;
7369 if (m_pEditRouteArray) {
7370 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7371 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7372 if (pr->IsVisible()) {
7373 brp_viz = true;
7374 break;
7375 }
7376 }
7377 } else
7378 brp_viz = frp->IsVisible(); // isolated point
7379
7380 if (brp_viz) {
7381 // Use route array to rubberband all affected routes
7382 if (m_pEditRouteArray) // Editing Waypoint as part of route
7383 {
7384 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7385 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7386 pr->m_bIsBeingEdited = setBeingEdited;
7387 }
7388 m_bRouteEditing = setBeingEdited;
7389 } else // editing Mark
7390 {
7391 frp->m_bRPIsBeingEdited = setBeingEdited;
7392 m_bMarkEditing = setBeingEdited;
7393 }
7394
7395 m_pRoutePointEditTarget = frp;
7396 m_pFoundPoint = pFind;
7397 break; // out of the while(node)
7398 }
7399 } // for (SelectItem...
7400}
7401std::shared_ptr<PI_PointContext> ChartCanvas::GetCanvasContextAtPoint(int x,
7402 int y) {
7403 // General Right Click
7404 // Look for selectable objects
7405 double slat, slon;
7406 GetCanvasPixPoint(x, y, slat, slon);
7407
7408 SelectItem *pFindAIS;
7409 SelectItem *pFindRP;
7410 SelectItem *pFindRouteSeg;
7411 SelectItem *pFindTrackSeg;
7412 SelectItem *pFindCurrent = NULL;
7413 SelectItem *pFindTide = NULL;
7414
7415 // Get all the selectable things at the selected point
7416 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7417 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7418 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7419 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7420 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7421
7422 if (m_bShowCurrent)
7423 pFindCurrent =
7424 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7425
7426 if (m_bShowTide) // look for tide stations
7427 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7428
7429 int seltype = 0;
7430
7431 // Try for AIS targets first
7432 int FoundAIS_MMSI = 0;
7433 if (pFindAIS) {
7434 FoundAIS_MMSI = pFindAIS->GetUserData();
7435
7436 // Make sure the target data is available
7437 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7438 seltype |= SELTYPE_AISTARGET;
7439 }
7440
7441 // Now the various Route Parts
7442
7443 RoutePoint *FoundRoutePoint = NULL;
7444 Route *SelectedRoute = NULL;
7445
7446 if (pFindRP) {
7447 RoutePoint *pFirstVizPoint = NULL;
7448 RoutePoint *pFoundActiveRoutePoint = NULL;
7449 RoutePoint *pFoundVizRoutePoint = NULL;
7450 Route *pSelectedActiveRoute = NULL;
7451 Route *pSelectedVizRoute = NULL;
7452
7453 // There is at least one routepoint, so get the whole list
7454 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7455 SelectableItemList SelList =
7456 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7457 for (SelectItem *pFindSel : SelList) {
7458 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7459
7460 // Get an array of all routes using this point
7461 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7462
7463 // Use route array (if any) to determine actual visibility for this point
7464 bool brp_viz = false;
7465 if (proute_array) {
7466 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7467 Route *pr = (Route *)proute_array->Item(ir);
7468 if (pr->IsVisible()) {
7469 brp_viz = true;
7470 break;
7471 }
7472 }
7473 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7474 // but still exists as a waypoint
7475 brp_viz = prp->IsVisible(); // so treat as isolated point
7476
7477 } else
7478 brp_viz = prp->IsVisible(); // isolated point
7479
7480 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7481
7482 // Use route array to choose the appropriate route
7483 // Give preference to any active route, otherwise select the first visible
7484 // route in the array for this point
7485 if (proute_array) {
7486 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7487 Route *pr = (Route *)proute_array->Item(ir);
7488 if (pr->m_bRtIsActive) {
7489 pSelectedActiveRoute = pr;
7490 pFoundActiveRoutePoint = prp;
7491 break;
7492 }
7493 }
7494
7495 if (NULL == pSelectedVizRoute) {
7496 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7497 Route *pr = (Route *)proute_array->Item(ir);
7498 if (pr->IsVisible()) {
7499 pSelectedVizRoute = pr;
7500 pFoundVizRoutePoint = prp;
7501 break;
7502 }
7503 }
7504 }
7505
7506 delete proute_array;
7507 }
7508 }
7509
7510 // Now choose the "best" selections
7511 if (pFoundActiveRoutePoint) {
7512 FoundRoutePoint = pFoundActiveRoutePoint;
7513 SelectedRoute = pSelectedActiveRoute;
7514 } else if (pFoundVizRoutePoint) {
7515 FoundRoutePoint = pFoundVizRoutePoint;
7516 SelectedRoute = pSelectedVizRoute;
7517 } else
7518 // default is first visible point in list
7519 FoundRoutePoint = pFirstVizPoint;
7520
7521 if (SelectedRoute) {
7522 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7523 } else if (FoundRoutePoint) {
7524 seltype |= SELTYPE_MARKPOINT;
7525 }
7526
7527 // Highlight the selected point, to verify the proper right click selection
7528#if 0
7529 if (m_pFoundRoutePoint) {
7530 m_pFoundRoutePoint->m_bPtIsSelected = true;
7531 wxRect wp_rect;
7532 RoutePointGui(*m_pFoundRoutePoint)
7533 .CalculateDCRect(m_dc_route, this, &wp_rect);
7534 RefreshRect(wp_rect, true);
7535 }
7536#endif
7537 }
7538
7539 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7540 // routes But call the popup handler with identifier appropriate to the type
7541 if (pFindRouteSeg) // there is at least one select item
7542 {
7543 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7544 SelectableItemList SelList =
7545 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7546
7547 if (NULL == SelectedRoute) // the case where a segment only is selected
7548 {
7549 // Choose the first visible route containing segment in the list
7550 for (SelectItem *pFindSel : SelList) {
7551 Route *pr = (Route *)pFindSel->m_pData3;
7552 if (pr->IsVisible()) {
7553 SelectedRoute = pr;
7554 break;
7555 }
7556 }
7557 }
7558
7559 if (SelectedRoute) {
7560 if (NULL == FoundRoutePoint)
7561 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7562
7563 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7564 seltype |= SELTYPE_ROUTESEGMENT;
7565 }
7566 }
7567
7568#if 0
7569 if (pFindTrackSeg) {
7570 m_pSelectedTrack = NULL;
7571 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7572 SelectableItemList SelList =
7573 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7574
7575 // Choose the first visible track containing segment in the list
7576 wxSelectableItemListNode *node = SelList.GetFirst();
7577 while (node) {
7578 SelectItem *pFindSel = node->GetData();
7579
7580 Track *pt = (Track *)pFindSel->m_pData3;
7581 if (pt->IsVisible()) {
7582 m_pSelectedTrack = pt;
7583 break;
7584 }
7585 node = node->GetNext();
7586 }
7587
7588 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7589 }
7590#endif
7591
7592#if 0
7593 bool bseltc = false;
7594 // if(0 == seltype)
7595 {
7596 if (pFindCurrent) {
7597 // There may be multiple current entries at the same point.
7598 // For example, there often is a current substation (with directions
7599 // specified) co-located with its master. We want to select the
7600 // substation, so that the direction will be properly indicated on the
7601 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
7602 // substation)
7603 IDX_entry *pIDX_best_candidate;
7604
7605 SelectItem *pFind = NULL;
7606 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7607 SelectableItemList SelList = pSelectTC->FindSelectionList(
7608 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_CURRENTPOINT);
7609
7610 // Default is first entry
7611 wxSelectableItemListNode *node = SelList.GetFirst();
7612 pFind = node->GetData();
7613 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7614
7615 if (SelList.GetCount() > 1) {
7616 node = node->GetNext();
7617 while (node) {
7618 pFind = node->GetData();
7619 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
7620 if (pIDX_candidate->IDX_type == 'c') {
7621 pIDX_best_candidate = pIDX_candidate;
7622 break;
7623 }
7624
7625 node = node->GetNext();
7626 } // while (node)
7627 } else {
7628 wxSelectableItemListNode *node = SelList.GetFirst();
7629 pFind = node->GetData();
7630 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7631 }
7632
7633 m_pIDXCandidate = pIDX_best_candidate;
7634
7635 if (0 == seltype) {
7636 DrawTCWindow(x, y, (void *)pIDX_best_candidate);
7637 Refresh(false);
7638 bseltc = true;
7639 } else
7640 seltype |= SELTYPE_CURRENTPOINT;
7641 }
7642
7643 else if (pFindTide) {
7644 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
7645
7646 if (0 == seltype) {
7647 DrawTCWindow(x, y, (void *)pFindTide->m_pData1);
7648 Refresh(false);
7649 bseltc = true;
7650 } else
7651 seltype |= SELTYPE_TIDEPOINT;
7652 }
7653 }
7654#endif
7655
7656 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7657
7658 // Populate the return struct
7659 auto rstruct = std::make_shared<PI_PointContext>();
7660 rstruct->object_type = OBJECT_CHART;
7661 rstruct->object_ident = "";
7662
7663 if (seltype == SELTYPE_AISTARGET) {
7664 rstruct->object_type = OBJECT_AISTARGET;
7665 wxString val;
7666 val.Printf("%d", FoundAIS_MMSI);
7667 rstruct->object_ident = val.ToStdString();
7668 } else if (seltype & SELTYPE_MARKPOINT) {
7669 if (FoundRoutePoint) {
7670 rstruct->object_type = OBJECT_ROUTEPOINT;
7671 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7672 }
7673 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7674 if (SelectedRoute) {
7675 rstruct->object_type = OBJECT_ROUTESEGMENT;
7676 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7677 }
7678 }
7679
7680 return rstruct;
7681}
7682
7683void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7684 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7685 singleClickEventIsValid = false;
7686 m_DoubleClickTimer->Stop();
7687}
7688
7689bool leftIsDown;
7690
7691bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7692 if (!m_bChartDragging && !m_bDrawingRoute) {
7693 /*
7694 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7695 * mouse event coordinates are in logical pixels.
7696 */
7697 if (m_Compass && m_Compass->IsShown()) {
7698 wxRect logicalRect = m_Compass->GetLogicalRect();
7699 bool isInCompass = logicalRect.Contains(event.GetPosition());
7700 if (isInCompass || m_mouseWasInCompass) {
7701 if (m_Compass->MouseEvent(event)) {
7702 cursor_region = CENTER;
7703 if (!g_btouch) SetCanvasCursor(event);
7704 m_mouseWasInCompass = isInCompass;
7705 return true;
7706 }
7707 }
7708 m_mouseWasInCompass = isInCompass;
7709 }
7710
7711 if (m_notification_button && m_notification_button->IsShown()) {
7712 wxRect logicalRect = m_notification_button->GetLogicalRect();
7713 bool isinButton = logicalRect.Contains(event.GetPosition());
7714 if (isinButton) {
7715 SetCursor(*pCursorArrow);
7716 if (event.LeftDown()) HandleNotificationMouseClick();
7717 return true;
7718 }
7719 }
7720
7721 if (MouseEventToolbar(event)) return true;
7722
7723 if (MouseEventChartBar(event)) return true;
7724
7725 if (MouseEventMUIBar(event)) return true;
7726
7727 if (MouseEventIENCBar(event)) return true;
7728 }
7729 return false;
7730}
7731
7732void ChartCanvas::HandleNotificationMouseClick() {
7733 if (!m_NotificationsList) {
7734 m_NotificationsList = new NotificationsList(this);
7735
7736 // calculate best size for Notification list
7737 m_NotificationsList->RecalculateSize();
7738 m_NotificationsList->Hide();
7739 }
7740
7741 if (m_NotificationsList->IsShown()) {
7742 m_NotificationsList->Hide();
7743 } else {
7744 m_NotificationsList->RecalculateSize();
7745 m_NotificationsList->ReloadNotificationList();
7746 m_NotificationsList->Show();
7747 }
7748}
7749bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7750 if (!g_bShowChartBar) return false;
7751
7752 if (!m_Piano->MouseEvent(event)) return false;
7753
7754 cursor_region = CENTER;
7755 if (!g_btouch) SetCanvasCursor(event);
7756 return true;
7757}
7758
7759bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7760 if (!IsPrimaryCanvas()) return false;
7761
7762 if (g_MainToolbar) {
7763 if (!g_MainToolbar->MouseEvent(event))
7764 return false;
7765 else
7766 g_MainToolbar->RefreshToolbar();
7767 }
7768
7769 cursor_region = CENTER;
7770 if (!g_btouch) SetCanvasCursor(event);
7771 return true;
7772}
7773
7774bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7775 if (!IsPrimaryCanvas()) return false;
7776
7777 if (g_iENCToolbar) {
7778 if (!g_iENCToolbar->MouseEvent(event))
7779 return false;
7780 else {
7781 g_iENCToolbar->RefreshToolbar();
7782 return true;
7783 }
7784 }
7785 return false;
7786}
7787
7788bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7789 if (m_muiBar) {
7790 if (!m_muiBar->MouseEvent(event)) return false;
7791 }
7792
7793 cursor_region = CENTER;
7794 if (!g_btouch) SetCanvasCursor(event);
7795 if (m_muiBar)
7796 return true;
7797 else
7798 return false;
7799}
7800
7801bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7802 int x, y;
7803
7804 bool bret = false;
7805
7806 event.GetPosition(&x, &y);
7807
7808 x *= m_displayScale;
7809 y *= m_displayScale;
7810
7811 m_MouseDragging = event.Dragging();
7812
7813 // Some systems produce null drag events, where the pointer position has not
7814 // changed from the previous value. Detect this case, and abort further
7815 // processing (FS#1748)
7816#ifdef __WXMSW__
7817 if (event.Dragging()) {
7818 if ((x == mouse_x) && (y == mouse_y)) return true;
7819 }
7820#endif
7821
7822 mouse_x = x;
7823 mouse_y = y;
7824 mouse_leftisdown = event.LeftDown();
7826
7827 // Establish the event region
7828 cursor_region = CENTER;
7829
7830 int chartbar_height = GetChartbarHeight();
7831
7832 if (m_Compass && m_Compass->IsShown() &&
7833 m_Compass->GetRect().Contains(event.GetPosition())) {
7834 cursor_region = CENTER;
7835 } else if (x > xr_margin) {
7836 cursor_region = MID_RIGHT;
7837 } else if (x < xl_margin) {
7838 cursor_region = MID_LEFT;
7839 } else if (y > yb_margin - chartbar_height &&
7840 y < m_canvas_height - chartbar_height) {
7841 cursor_region = MID_TOP;
7842 } else if (y < yt_margin) {
7843 cursor_region = MID_BOT;
7844 } else {
7845 cursor_region = CENTER;
7846 }
7847
7848 if (!g_btouch) SetCanvasCursor(event);
7849
7850 // Protect from leftUp's coming from event handlers in child
7851 // windows who return focus to the canvas.
7852 leftIsDown = event.LeftDown();
7853
7854#ifndef __WXOSX__
7855 if (event.LeftDown()) {
7856 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7857 // The menu bar is temporarily visible due to alt having been pressed.
7858 // Clicking will hide it, and do nothing else.
7859 g_bTempShowMenuBar = false;
7860 parent_frame->ApplyGlobalSettings(false);
7861 return (true);
7862 }
7863 }
7864#endif
7865
7866 // Update modifiers here; some window managers never send the key event
7867 m_modkeys = 0;
7868 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7869 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7870
7871#ifdef __WXMSW__
7872 // TODO Test carefully in other platforms, remove ifdef....
7873 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7874 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7875#endif
7876
7877 event.SetEventObject(this);
7878 if (SendMouseEventToPlugins(event))
7879 return (true); // PlugIn did something, and does not want the canvas to
7880 // do anything else
7881
7882 // Capture LeftUp's and time them, unless it already came from the timer.
7883
7884 // Detect end of chart dragging
7885 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7886 StartChartDragInertia();
7887 }
7888
7889 if (b_handle_dclick && event.LeftUp() && !singleClickEventIsValid) {
7890 // Ignore the second LeftUp after the DClick.
7891 if (m_DoubleClickTimer->IsRunning()) {
7892 m_DoubleClickTimer->Stop();
7893 return (true);
7894 }
7895
7896 // Save the event for later running if there is no DClick.
7897 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7898 singleClickEvent = event;
7899 singleClickEventIsValid = true;
7900 return (true);
7901 }
7902
7903 // This logic is necessary on MSW to handle the case where
7904 // a context (right-click) menu is dismissed without action
7905 // by clicking on the chart surface.
7906 // We need to avoid an unintentional pan by eating some clicks...
7907#ifdef __WXMSW__
7908 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7909 if (g_click_stop > 0) {
7910 g_click_stop--;
7911 return (true);
7912 }
7913 }
7914#endif
7915
7916 // Kick off the Rotation control timer
7917 if (GetUpMode() == COURSE_UP_MODE) {
7918 m_b_rot_hidef = false;
7919 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7920 } else
7921 pRotDefTimer->Stop();
7922
7923 // Retrigger the route leg / AIS target popup timer
7924 bool bRoll = !g_btouch;
7925#ifdef __ANDROID__
7926 bRoll = g_bRollover;
7927#endif
7928 if (bRoll) {
7929 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7930 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7931 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7932 m_RolloverPopupTimer.Start(
7933 10,
7934 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7935 else
7936 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7937 }
7938
7939 // Retrigger the cursor tracking timer
7940 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7941
7942// Show cursor position on Status Bar, if present
7943// except for GTK, under which status bar updates are very slow
7944// due to Update() call.
7945// In this case, as a workaround, update the status window
7946// after an interval timer (pCurTrackTimer) pops, which will happen
7947// whenever the mouse has stopped moving for specified interval.
7948// See the method OnCursorTrackTimerEvent()
7949#if !defined(__WXGTK__) && !defined(__WXQT__)
7950 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7951#endif
7952
7953 // Send the current cursor lat/lon to all PlugIns requesting it
7954 if (g_pi_manager) {
7955 // Occasionally, MSW will produce nonsense events on right click....
7956 // This results in an error in cursor geo position, so we skip this case
7957 if ((x >= 0) && (y >= 0))
7958 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7959 }
7960
7961 if (!g_btouch) {
7962 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7963 wxPoint p = ClientToScreen(wxPoint(x, y));
7964 }
7965 }
7966
7967 if (1 ) {
7968 // Route Creation Rubber Banding
7969 if (m_routeState >= 2) {
7970 r_rband.x = x;
7971 r_rband.y = y;
7972 m_bDrawingRoute = true;
7973
7974 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7975 Refresh(false);
7976 }
7977
7978 // Measure Tool Rubber Banding
7979 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7980 r_rband.x = x;
7981 r_rband.y = y;
7982 m_bDrawingRoute = true;
7983
7984 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7985 Refresh(false);
7986 }
7987 }
7988 return bret;
7989}
7990
7991int ChartCanvas::PrepareContextSelections(double lat, double lon) {
7992 // On general Right Click
7993 // Look for selectable objects
7994 double slat = lat;
7995 double slon = lon;
7996
7997#if defined(__WXMAC__) || defined(__ANDROID__)
7998 wxScreenDC sdc;
7999 ocpnDC dc(sdc);
8000#else
8001 wxClientDC cdc(GetParent());
8002 ocpnDC dc(cdc);
8003#endif
8004
8005 SelectItem *pFindAIS;
8006 SelectItem *pFindRP;
8007 SelectItem *pFindRouteSeg;
8008 SelectItem *pFindTrackSeg;
8009 SelectItem *pFindCurrent = NULL;
8010 SelectItem *pFindTide = NULL;
8011
8012 // Deselect any current objects
8013 if (m_pSelectedRoute) {
8014 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
8015 m_pSelectedRoute->DeSelectRoute();
8016#ifdef ocpnUSE_GL
8017 if (g_bopengl && m_glcc) {
8018 InvalidateGL();
8019 Update();
8020 } else
8021#endif
8022 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8023 }
8024
8025 if (m_pFoundRoutePoint) {
8026 m_pFoundRoutePoint->m_bPtIsSelected = false;
8027 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8028 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8029 }
8030
8033 if (g_btouch && m_pRoutePointEditTarget) {
8034 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8035 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8036 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8037 }
8038
8039 // Get all the selectable things at the cursor
8040 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8041 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8042 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8043 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8044 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8045
8046 if (m_bShowCurrent)
8047 pFindCurrent =
8048 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8049
8050 if (m_bShowTide) // look for tide stations
8051 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8052
8053 int seltype = 0;
8054
8055 // Try for AIS targets first
8056 if (pFindAIS) {
8057 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8058
8059 // Make sure the target data is available
8060 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8061 seltype |= SELTYPE_AISTARGET;
8062 }
8063
8064 // Now examine the various Route parts
8065
8066 m_pFoundRoutePoint = NULL;
8067 if (pFindRP) {
8068 RoutePoint *pFirstVizPoint = NULL;
8069 RoutePoint *pFoundActiveRoutePoint = NULL;
8070 RoutePoint *pFoundVizRoutePoint = NULL;
8071 Route *pSelectedActiveRoute = NULL;
8072 Route *pSelectedVizRoute = NULL;
8073
8074 // There is at least one routepoint, so get the whole list
8075 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8076 SelectableItemList SelList =
8077 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8078 for (SelectItem *pFindSel : SelList) {
8079 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8080
8081 // Get an array of all routes using this point
8082 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8083
8084 // Use route array (if any) to determine actual visibility for this point
8085 bool brp_viz = false;
8086 if (proute_array) {
8087 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8088 Route *pr = (Route *)proute_array->Item(ir);
8089 if (pr->IsVisible()) {
8090 brp_viz = true;
8091 break;
8092 }
8093 }
8094 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8095 // but still exists as a waypoint
8096 brp_viz = prp->IsVisible(); // so treat as isolated point
8097
8098 } else
8099 brp_viz = prp->IsVisible(); // isolated point
8100
8101 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8102
8103 // Use route array to choose the appropriate route
8104 // Give preference to any active route, otherwise select the first visible
8105 // route in the array for this point
8106 m_pSelectedRoute = NULL;
8107 if (proute_array) {
8108 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8109 Route *pr = (Route *)proute_array->Item(ir);
8110 if (pr->m_bRtIsActive) {
8111 pSelectedActiveRoute = pr;
8112 pFoundActiveRoutePoint = prp;
8113 break;
8114 }
8115 }
8116
8117 if (NULL == pSelectedVizRoute) {
8118 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8119 Route *pr = (Route *)proute_array->Item(ir);
8120 if (pr->IsVisible()) {
8121 pSelectedVizRoute = pr;
8122 pFoundVizRoutePoint = prp;
8123 break;
8124 }
8125 }
8126 }
8127
8128 delete proute_array;
8129 }
8130 }
8131
8132 // Now choose the "best" selections
8133 if (pFoundActiveRoutePoint) {
8134 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8135 m_pSelectedRoute = pSelectedActiveRoute;
8136 } else if (pFoundVizRoutePoint) {
8137 m_pFoundRoutePoint = pFoundVizRoutePoint;
8138 m_pSelectedRoute = pSelectedVizRoute;
8139 } else
8140 // default is first visible point in list
8141 m_pFoundRoutePoint = pFirstVizPoint;
8142
8143 if (m_pSelectedRoute) {
8144 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8145 } else if (m_pFoundRoutePoint) {
8146 seltype |= SELTYPE_MARKPOINT;
8147 }
8148
8149 // Highlight the selected point, to verify the proper right click selection
8150 if (m_pFoundRoutePoint) {
8151 m_pFoundRoutePoint->m_bPtIsSelected = true;
8152 wxRect wp_rect;
8153 RoutePointGui(*m_pFoundRoutePoint)
8154 .CalculateDCRect(m_dc_route, this, &wp_rect);
8155 RefreshRect(wp_rect, true);
8156 }
8157 }
8158
8159 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8160 // routes But call the popup handler with identifier appropriate to the type
8161 if (pFindRouteSeg) // there is at least one select item
8162 {
8163 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8164 SelectableItemList SelList =
8165 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8166
8167 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8168 {
8169 // Choose the first visible route containing segment in the list
8170 for (SelectItem *pFindSel : SelList) {
8171 Route *pr = (Route *)pFindSel->m_pData3;
8172 if (pr->IsVisible()) {
8173 m_pSelectedRoute = pr;
8174 break;
8175 }
8176 }
8177 }
8178
8179 if (m_pSelectedRoute) {
8180 if (NULL == m_pFoundRoutePoint)
8181 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8182
8183 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8184 if (m_pSelectedRoute->m_bRtIsSelected) {
8185#ifdef ocpnUSE_GL
8186 if (g_bopengl && m_glcc) {
8187 InvalidateGL();
8188 Update();
8189 } else
8190#endif
8191 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8192 }
8193 seltype |= SELTYPE_ROUTESEGMENT;
8194 }
8195 }
8196
8197 if (pFindTrackSeg) {
8198 m_pSelectedTrack = NULL;
8199 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8200 SelectableItemList SelList =
8201 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8202
8203 // Choose the first visible track containing segment in the list
8204 for (SelectItem *pFindSel : SelList) {
8205 Track *pt = (Track *)pFindSel->m_pData3;
8206 if (pt->IsVisible()) {
8207 m_pSelectedTrack = pt;
8208 break;
8209 }
8210 }
8211 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8212 }
8213
8214 {
8215 if (pFindCurrent) {
8216 // There may be multiple current entries at the same point.
8217 // For example, there often is a current substation (with directions
8218 // specified) co-located with its master. We want to select the
8219 // substation, so that the direction will be properly indicated on the
8220 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8221 // substation)
8222 IDX_entry *pIDX_best_candidate;
8223
8224 SelectItem *pFind = NULL;
8225 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8226 SelectableItemList SelList =
8227 pSelectTC->FindSelectionList(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8228
8229 // Default is first entry
8230 pFind = *SelList.begin();
8231 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8232
8233 auto node = SelList.begin();
8234 if (SelList.size() > 1) {
8235 for (++node; node != SelList.end(); ++node) {
8236 pFind = *node;
8237 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8238 if (pIDX_candidate->IDX_type == 'c') {
8239 pIDX_best_candidate = pIDX_candidate;
8240 break;
8241 }
8242 } // while (node)
8243 } else {
8244 pFind = *SelList.begin();
8245 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8246 }
8247
8248 m_pIDXCandidate = pIDX_best_candidate;
8249 seltype |= SELTYPE_CURRENTPOINT;
8250 }
8251
8252 else if (pFindTide) {
8253 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8254 seltype |= SELTYPE_TIDEPOINT;
8255 }
8256 }
8257
8258 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8259
8260 return seltype;
8261}
8262
8263void ChartCanvas::CallPopupMenu(int x, int y) {
8264 last_drag.x = x;
8265 last_drag.y = y;
8266 if (m_routeState) { // creating route?
8267 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8268 return;
8269 }
8270
8272
8273 // If tide or current point is selected, then show the TC dialog immediately
8274 // without context menu
8275 if (SELTYPE_CURRENTPOINT == seltype) {
8276 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8277 Refresh(false);
8278 return;
8279 }
8280
8281 if (SELTYPE_TIDEPOINT == seltype) {
8282 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8283 Refresh(false);
8284 return;
8285 }
8286
8287 InvokeCanvasMenu(x, y, seltype);
8288
8289 // Clean up if not deleted in InvokeCanvasMenu
8290 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8291 m_pSelectedRoute->m_bRtIsSelected = false;
8292 }
8293
8294 m_pSelectedRoute = NULL;
8295
8296 if (m_pFoundRoutePoint) {
8297 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8298 m_pFoundRoutePoint->m_bPtIsSelected = false;
8299 }
8300 m_pFoundRoutePoint = NULL;
8301
8302 Refresh(true);
8303 // Refresh(false); // needed for MSW, not GTK Why??
8304}
8305
8306bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8307 // For now just bail out completely if the point clicked is not on the chart
8308 if (std::isnan(m_cursor_lat)) return false;
8309
8310 // Mouse Clicks
8311 bool ret = false; // return true if processed
8312
8313 int x, y, mx, my;
8314 event.GetPosition(&x, &y);
8315 mx = x;
8316 my = y;
8317
8318 // Calculate meaningful SelectRadius
8319 float SelectRadius;
8320 SelectRadius = g_Platform->GetSelectRadiusPix() /
8321 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8322
8324 // We start with Double Click processing. The first left click just starts a
8325 // timer and is remembered, then we actually do something if there is a
8326 // LeftDClick. If there is, the two single clicks are ignored.
8327
8328 if (event.LeftDClick() && (cursor_region == CENTER)) {
8329 m_DoubleClickTimer->Start();
8330 singleClickEventIsValid = false;
8331
8332 double zlat, zlon;
8334 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8335
8336 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8337 if (m_bShowAIS) {
8338 SelectItem *pFindAIS;
8339 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8340
8341 if (pFindAIS) {
8342 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8343 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8344 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8345 }
8346 return true;
8347 }
8348 }
8349
8350 SelectableItemList rpSelList =
8351 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8352 bool b_onRPtarget = false;
8353 for (SelectItem *pFind : rpSelList) {
8354 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8355 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8356 b_onRPtarget = true;
8357 break;
8358 }
8359 }
8360
8361 // Double tap with selected RoutePoint or Mark
8362
8363 if (m_pRoutePointEditTarget) {
8364 if (b_onRPtarget) {
8365 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8366 return true;
8367 } else {
8368 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8369 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8370 if (g_btouch)
8371 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8372 wxRect wp_rect;
8373 RoutePointGui(*m_pRoutePointEditTarget)
8374 .CalculateDCRect(m_dc_route, this, &wp_rect);
8375 m_pRoutePointEditTarget = NULL; // cancel selection
8376 RefreshRect(wp_rect, true);
8377 return true;
8378 }
8379 } else {
8380 auto node = rpSelList.begin();
8381 if (node != rpSelList.end()) {
8382 SelectItem *pFind = *node;
8383 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8384 if (frp) {
8385 wxArrayPtrVoid *proute_array =
8387
8388 // Use route array (if any) to determine actual visibility for this
8389 // point
8390 bool brp_viz = false;
8391 if (proute_array) {
8392 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8393 Route *pr = (Route *)proute_array->Item(ir);
8394 if (pr->IsVisible()) {
8395 brp_viz = true;
8396 break;
8397 }
8398 }
8399 delete proute_array;
8400 if (!brp_viz &&
8401 frp->IsShared()) // is not visible as part of route, but still
8402 // exists as a waypoint
8403 brp_viz = frp->IsVisible(); // so treat as isolated point
8404 } else
8405 brp_viz = frp->IsVisible(); // isolated point
8406
8407 if (brp_viz) {
8408 ShowMarkPropertiesDialog(frp);
8409 return true;
8410 }
8411 }
8412 }
8413 }
8414
8415 SelectItem *cursorItem;
8416 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8417
8418 if (cursorItem) {
8419 Route *pr = (Route *)cursorItem->m_pData3;
8420 if (pr->IsVisible()) {
8421 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8422 return true;
8423 }
8424 }
8425
8426 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8427
8428 if (cursorItem) {
8429 Track *pt = (Track *)cursorItem->m_pData3;
8430 if (pt->IsVisible()) {
8431 ShowTrackPropertiesDialog(pt);
8432 return true;
8433 }
8434 }
8435
8436 // Found no object to act on, so show chart info.
8437
8438 ShowObjectQueryWindow(x, y, zlat, zlon);
8439 return true;
8440 }
8441
8443 if (event.LeftDown()) {
8444 // This really should not be needed, but....
8445 // on Windows, when using wxAUIManager, sometimes the focus is lost
8446 // when clicking into another pane, e.g.the AIS target list, and then back
8447 // to this pane. Oddly, some mouse events are not lost, however. Like this
8448 // one....
8449 SetFocus();
8450
8451 last_drag.x = mx;
8452 last_drag.y = my;
8453 leftIsDown = true;
8454
8455 if (!g_btouch) {
8456 if (m_routeState) // creating route?
8457 {
8458 double rlat, rlon;
8459 bool appending = false;
8460 bool inserting = false;
8461 Route *tail = 0;
8462
8463 SetCursor(*pCursorPencil);
8464 rlat = m_cursor_lat;
8465 rlon = m_cursor_lon;
8466
8467 m_bRouteEditing = true;
8468
8469 if (m_routeState == 1) {
8470 m_pMouseRoute = new Route();
8471 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8472 pRouteList->push_back(m_pMouseRoute);
8473 r_rband.x = x;
8474 r_rband.y = y;
8475 }
8476
8477 // Check to see if there is a nearby point which may be reused
8478 RoutePoint *pMousePoint = NULL;
8479
8480 // Calculate meaningful SelectRadius
8481 double nearby_radius_meters =
8482 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8483
8484 RoutePoint *pNearbyPoint =
8485 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8486 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8487 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8488 wxArrayPtrVoid *proute_array =
8489 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8490
8491 // Use route array (if any) to determine actual visibility for this
8492 // point
8493 bool brp_viz = false;
8494 if (proute_array) {
8495 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8496 Route *pr = (Route *)proute_array->Item(ir);
8497 if (pr->IsVisible()) {
8498 brp_viz = true;
8499 break;
8500 }
8501 }
8502 delete proute_array;
8503 if (!brp_viz &&
8504 pNearbyPoint->IsShared()) // is not visible as part of route,
8505 // but still exists as a waypoint
8506 brp_viz =
8507 pNearbyPoint->IsVisible(); // so treat as isolated point
8508 } else
8509 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8510
8511 if (brp_viz) {
8512 wxString msg = _("Use nearby waypoint?");
8513 // Don't add a mark without name to the route. Name it if needed
8514 const bool noname(pNearbyPoint->GetName() == "");
8515 if (noname) {
8516 msg =
8517 _("Use nearby nameless waypoint and name it M with"
8518 " a unique number?");
8519 }
8520 // Avoid route finish on focus change for message dialog
8521 m_FinishRouteOnKillFocus = false;
8522 int dlg_return =
8523 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8524 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8525 m_FinishRouteOnKillFocus = true;
8526 if (dlg_return == wxID_YES) {
8527 if (noname) {
8528 if (m_pMouseRoute) {
8529 int last_wp_num = m_pMouseRoute->GetnPoints();
8530 // AP-ECRMB will truncate to 6 characters
8531 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8532 wxString wp_name = wxString::Format(
8533 "M%002i-%s", last_wp_num + 1, guid_short);
8534 pNearbyPoint->SetName(wp_name);
8535 } else
8536 pNearbyPoint->SetName("WPXX");
8537 }
8538 pMousePoint = pNearbyPoint;
8539
8540 // Using existing waypoint, so nothing to delete for undo.
8541 if (m_routeState > 1)
8542 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8543 Undo_HasParent, NULL);
8544
8545 tail =
8546 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8547 bool procede = false;
8548 if (tail) {
8549 procede = true;
8550 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8551 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8552 procede = false;
8553 }
8554
8555 if (procede) {
8556 int dlg_return;
8557 m_FinishRouteOnKillFocus = false;
8558 if (m_routeState ==
8559 1) { // first point in new route, preceeding route to be
8560 // added? Not touch case
8561
8562 wxString dmsg =
8563 _("Insert first part of this route in the new route?");
8564 if (tail->GetIndexOf(pMousePoint) ==
8565 tail->GetnPoints()) // Starting on last point of another
8566 // route?
8567 dmsg = _("Insert this route in the new route?");
8568
8569 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8570 dlg_return = OCPNMessageBox(
8571 this, dmsg, _("OpenCPN Route Create"),
8572 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8573 m_FinishRouteOnKillFocus = true;
8574
8575 if (dlg_return == wxID_YES) {
8576 inserting = true; // part of the other route will be
8577 // preceeding the new route
8578 }
8579 }
8580 } else {
8581 wxString dmsg =
8582 _("Append last part of this route to the new route?");
8583 if (tail->GetIndexOf(pMousePoint) == 1)
8584 dmsg = _(
8585 "Append this route to the new route?"); // Picking the
8586 // first point
8587 // of another
8588 // route?
8589
8590 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8591 dlg_return = OCPNMessageBox(
8592 this, dmsg, _("OpenCPN Route Create"),
8593 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8594 m_FinishRouteOnKillFocus = true;
8595
8596 if (dlg_return == wxID_YES) {
8597 appending = true; // part of the other route will be
8598 // appended to the new route
8599 }
8600 }
8601 }
8602 }
8603
8604 // check all other routes to see if this point appears in any
8605 // other route If it appears in NO other route, then it should e
8606 // considered an isolated mark
8607 if (!FindRouteContainingWaypoint(pMousePoint))
8608 pMousePoint->SetShared(true);
8609 }
8610 }
8611 }
8612
8613 if (NULL == pMousePoint) { // need a new point
8614 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8615 "", wxEmptyString);
8616 pMousePoint->SetNameShown(false);
8617
8618 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8619
8620 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8621
8622 if (m_routeState > 1)
8623 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8624 Undo_IsOrphanded, NULL);
8625 }
8626
8627 if (m_pMouseRoute) {
8628 if (m_routeState == 1) {
8629 // First point in the route.
8630 m_pMouseRoute->AddPoint(pMousePoint);
8631 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8632 } else {
8633 if (m_pMouseRoute->m_NextLegGreatCircle) {
8634 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8635 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8636 &rhumbBearing, &rhumbDist);
8637 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8638 rlat, &gcDist, &gcBearing, NULL);
8639 double gcDistNM = gcDist / 1852.0;
8640
8641 // Empirically found expression to get reasonable route segments.
8642 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8643 pow(rhumbDist - gcDistNM - 1, 0.5);
8644
8645 wxString msg;
8646 msg << _("For this leg the Great Circle route is ")
8647 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8648 << _(" shorter than rhumbline.\n\n")
8649 << _("Would you like include the Great Circle routing points "
8650 "for this leg?");
8651
8652 m_FinishRouteOnKillFocus = false;
8653 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8654 // does not fully capture mouse
8655
8656 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8657 wxYES_NO | wxNO_DEFAULT);
8658
8659 m_disable_edge_pan = false;
8660 m_FinishRouteOnKillFocus = true;
8661
8662 if (answer == wxID_YES) {
8663 RoutePoint *gcPoint;
8664 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8665 wxRealPoint gcCoord;
8666
8667 for (int i = 1; i <= segmentCount; i++) {
8668 double fraction = (double)i * (1.0 / (double)segmentCount);
8669 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8670 gcDist * fraction, gcBearing,
8671 &gcCoord.x, &gcCoord.y, NULL);
8672
8673 if (i < segmentCount) {
8674 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8675 wxEmptyString);
8676 gcPoint->SetNameShown(false);
8677 // pConfig->AddNewWayPoint(gcPoint, -1);
8678 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8679
8680 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8681 gcPoint);
8682 } else {
8683 gcPoint = pMousePoint; // Last point, previously exsisting!
8684 }
8685
8686 m_pMouseRoute->AddPoint(gcPoint);
8687 pSelect->AddSelectableRouteSegment(
8688 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8689 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8690 prevGcPoint = gcPoint;
8691 }
8692
8693 undo->CancelUndoableAction(true);
8694
8695 } else {
8696 m_pMouseRoute->AddPoint(pMousePoint);
8697 pSelect->AddSelectableRouteSegment(
8698 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8699 pMousePoint, m_pMouseRoute);
8700 undo->AfterUndoableAction(m_pMouseRoute);
8701 }
8702 } else {
8703 // Ordinary rhumblinesegment.
8704 m_pMouseRoute->AddPoint(pMousePoint);
8705 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8706 rlon, m_prev_pMousePoint,
8707 pMousePoint, m_pMouseRoute);
8708 undo->AfterUndoableAction(m_pMouseRoute);
8709 }
8710 }
8711 }
8712 m_prev_rlat = rlat;
8713 m_prev_rlon = rlon;
8714 m_prev_pMousePoint = pMousePoint;
8715 if (m_pMouseRoute)
8716 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8717
8718 m_routeState++;
8719
8720 if (appending ||
8721 inserting) { // Appending a route or making a new route
8722 int connect = tail->GetIndexOf(pMousePoint);
8723 if (connect == 1) {
8724 inserting = false; // there is nothing to insert
8725 appending = true; // so append
8726 }
8727 int length = tail->GetnPoints();
8728
8729 int i;
8730 int start, stop;
8731 if (appending) {
8732 start = connect + 1;
8733 stop = length;
8734 } else { // inserting
8735 start = 1;
8736 stop = connect;
8737 m_pMouseRoute->RemovePoint(
8738 m_pMouseRoute
8739 ->GetLastPoint()); // Remove the first and only point
8740 }
8741 for (i = start; i <= stop; i++) {
8742 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8743 if (m_pMouseRoute)
8744 m_pMouseRoute->m_lastMousePointIndex =
8745 m_pMouseRoute->GetnPoints();
8746 m_routeState++;
8747 gFrame->RefreshAllCanvas();
8748 ret = true;
8749 }
8750 m_prev_rlat =
8751 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8752 m_prev_rlon =
8753 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8754 m_pMouseRoute->FinalizeForRendering();
8755 }
8756 gFrame->RefreshAllCanvas();
8757 ret = true;
8758 }
8759
8760 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8761 {
8762 SetCursor(*pCursorPencil);
8763
8764 if (!m_pMeasureRoute) {
8765 m_pMeasureRoute = new Route();
8766 pRouteList->push_back(m_pMeasureRoute);
8767 }
8768
8769 if (m_nMeasureState == 1) {
8770 r_rband.x = x;
8771 r_rband.y = y;
8772 }
8773
8774 RoutePoint *pMousePoint =
8775 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8776 wxEmptyString, wxEmptyString);
8777 pMousePoint->m_bShowName = false;
8778 pMousePoint->SetShowWaypointRangeRings(false);
8779
8780 m_pMeasureRoute->AddPoint(pMousePoint);
8781
8782 m_prev_rlat = m_cursor_lat;
8783 m_prev_rlon = m_cursor_lon;
8784 m_prev_pMousePoint = pMousePoint;
8785 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8786
8787 m_nMeasureState++;
8788 gFrame->RefreshAllCanvas();
8789 ret = true;
8790 }
8791
8792 else {
8793 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8794 }
8795 } // !g_btouch
8796 else { // g_btouch
8797
8798 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8799 // if near screen edge, pan with injection
8800 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8801 // return;
8802 // }
8803 }
8804 }
8805
8806 if (ret) return true;
8807 }
8808
8809 if (event.Dragging()) {
8810 // in touch screen mode ensure the finger/cursor is on the selected point's
8811 // radius to allow dragging
8812 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8813 if (g_btouch) {
8814 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8815 SelectItem *pFind = NULL;
8816 SelectableItemList SelList = pSelect->FindSelectionList(
8817 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8818 for (SelectItem *pFind : SelList) {
8819 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8820 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8821 }
8822 }
8823
8824 // Check for use of dragHandle
8825 if (m_pRoutePointEditTarget &&
8826 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8827 SelectItem *pFind = NULL;
8828 SelectableItemList SelList = pSelect->FindSelectionList(
8829 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8830 for (SelectItem *pFind : SelList) {
8831 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8832 if (m_pRoutePointEditTarget == frp) {
8833 m_bIsInRadius = true;
8834 break;
8835 }
8836 }
8837
8838 if (!m_dragoffsetSet) {
8839 RoutePointGui(*m_pRoutePointEditTarget)
8840 .PresetDragOffset(this, mouse_x, mouse_y);
8841 m_dragoffsetSet = true;
8842 }
8843 }
8844 }
8845
8846 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8847 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8848
8849 if (NULL == g_pMarkInfoDialog) {
8850 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8851 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8852 DraggingAllowed = false;
8853
8854 if (m_pRoutePointEditTarget &&
8855 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8856 DraggingAllowed = false;
8857
8858 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8859
8860 if (DraggingAllowed) {
8861 if (!undo->InUndoableAction()) {
8862 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8863 Undo_NeedsCopy, m_pFoundPoint);
8864 }
8865
8866 // Get the update rectangle for the union of the un-edited routes
8867 wxRect pre_rect;
8868
8869 if (!g_bopengl && m_pEditRouteArray) {
8870 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8871 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8872 // Need to validate route pointer
8873 // Route may be gone due to drgging close to ownship with
8874 // "Delete On Arrival" state set, as in the case of
8875 // navigating to an isolated waypoint on a temporary route
8876 if (g_pRouteMan->IsRouteValid(pr)) {
8877 wxRect route_rect;
8878 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8879 pre_rect.Union(route_rect);
8880 }
8881 }
8882 }
8883
8884 double new_cursor_lat = m_cursor_lat;
8885 double new_cursor_lon = m_cursor_lon;
8886
8887 if (CheckEdgePan(x, y, true, 5, 2))
8888 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8889
8890 // update the point itself
8891 if (g_btouch) {
8892 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8893 // new_cursor_lat, new_cursor_lon);
8894 RoutePointGui(*m_pRoutePointEditTarget)
8895 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8896 // update the Drag Handle entry in the pSelect list
8897 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8898 m_pRoutePointEditTarget,
8899 SELTYPE_DRAGHANDLE);
8900 m_pFoundPoint->m_slat =
8901 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8902 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8903 } else {
8904 m_pRoutePointEditTarget->m_lat =
8905 new_cursor_lat; // update the RoutePoint entry
8906 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8907 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8908 m_pFoundPoint->m_slat =
8909 new_cursor_lat; // update the SelectList entry
8910 m_pFoundPoint->m_slon = new_cursor_lon;
8911 }
8912
8913 // Update the MarkProperties Dialog, if currently shown
8914 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8915 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8916 g_pMarkInfoDialog->UpdateProperties(true);
8917 }
8918
8919 if (g_bopengl) {
8920 // InvalidateGL();
8921 Refresh(false);
8922 } else {
8923 // Get the update rectangle for the edited route
8924 wxRect post_rect;
8925
8926 if (m_pEditRouteArray) {
8927 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8928 ir++) {
8929 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8930 if (g_pRouteMan->IsRouteValid(pr)) {
8931 wxRect route_rect;
8932 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8933 post_rect.Union(route_rect);
8934 }
8935 }
8936 }
8937
8938 // Invalidate the union region
8939 pre_rect.Union(post_rect);
8940 RefreshRect(pre_rect, false);
8941 }
8942 gFrame->RefreshCanvasOther(this);
8943 m_bRoutePoinDragging = true;
8944 }
8945 ret = true;
8946 } // if Route Editing
8947
8948 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8949 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8950
8951 if (NULL == g_pMarkInfoDialog) {
8952 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8953 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8954 DraggingAllowed = false;
8955
8956 if (m_pRoutePointEditTarget &&
8957 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8958 DraggingAllowed = false;
8959
8960 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8961
8962 if (DraggingAllowed) {
8963 if (!undo->InUndoableAction()) {
8964 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8965 Undo_NeedsCopy, m_pFoundPoint);
8966 }
8967
8968 // The mark may be an anchorwatch
8969 double lpp1 = 0.;
8970 double lpp2 = 0.;
8971 double lppmax;
8972
8973 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8974 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8975 }
8976 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8977 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8978 }
8979 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8980
8981 // Get the update rectangle for the un-edited mark
8982 wxRect pre_rect;
8983 if (!g_bopengl) {
8984 RoutePointGui(*m_pRoutePointEditTarget)
8985 .CalculateDCRect(m_dc_route, this, &pre_rect);
8986 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8987 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8988 (int)(lppmax - (pre_rect.height / 2)));
8989 }
8990
8991 // update the point itself
8992 if (g_btouch) {
8993 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8994 // m_cursor_lat, m_cursor_lon);
8995 RoutePointGui(*m_pRoutePointEditTarget)
8996 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8997 // update the Drag Handle entry in the pSelect list
8998 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
8999 m_pRoutePointEditTarget,
9000 SELTYPE_DRAGHANDLE);
9001 m_pFoundPoint->m_slat =
9002 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9003 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9004 } else {
9005 m_pRoutePointEditTarget->m_lat =
9006 m_cursor_lat; // update the RoutePoint entry
9007 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9008 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9009 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9010 m_pFoundPoint->m_slon = m_cursor_lon;
9011 }
9012
9013 // Update the MarkProperties Dialog, if currently shown
9014 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9015 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9016 g_pMarkInfoDialog->UpdateProperties(true);
9017 }
9018
9019 // Invalidate the union region
9020 if (g_bopengl) {
9021 if (!g_btouch) InvalidateGL();
9022 Refresh(false);
9023 } else {
9024 // Get the update rectangle for the edited mark
9025 wxRect post_rect;
9026 RoutePointGui(*m_pRoutePointEditTarget)
9027 .CalculateDCRect(m_dc_route, this, &post_rect);
9028 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9029 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9030 (int)(lppmax - (post_rect.height / 2)));
9031
9032 // Invalidate the union region
9033 pre_rect.Union(post_rect);
9034 RefreshRect(pre_rect, false);
9035 }
9036 gFrame->RefreshCanvasOther(this);
9037 m_bRoutePoinDragging = true;
9038 }
9039 ret = true;
9040 }
9041
9042 if (ret) return true;
9043 } // dragging
9044
9045 if (event.LeftUp()) {
9046 bool b_startedit_route = false;
9047 m_dragoffsetSet = false;
9048
9049 if (g_btouch) {
9050 m_bChartDragging = false;
9051 m_bIsInRadius = false;
9052
9053 if (m_routeState) // creating route?
9054 {
9055 if (m_bedge_pan) {
9056 m_bedge_pan = false;
9057 return false;
9058 }
9059
9060 double rlat, rlon;
9061 bool appending = false;
9062 bool inserting = false;
9063 Route *tail = 0;
9064
9065 rlat = m_cursor_lat;
9066 rlon = m_cursor_lon;
9067
9068 if (m_pRoutePointEditTarget) {
9069 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9070 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9071 if (!g_bopengl) {
9072 wxRect wp_rect;
9073 RoutePointGui(*m_pRoutePointEditTarget)
9074 .CalculateDCRect(m_dc_route, this, &wp_rect);
9075 RefreshRect(wp_rect, true);
9076 }
9077 m_pRoutePointEditTarget = NULL;
9078 }
9079 m_bRouteEditing = true;
9080
9081 if (m_routeState == 1) {
9082 m_pMouseRoute = new Route();
9083 m_pMouseRoute->SetHiLite(50);
9084 pRouteList->push_back(m_pMouseRoute);
9085 r_rband.x = x;
9086 r_rband.y = y;
9087 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9088 }
9089
9090 // Check to see if there is a nearby point which may be reused
9091 RoutePoint *pMousePoint = NULL;
9092
9093 // Calculate meaningful SelectRadius
9094 double nearby_radius_meters =
9095 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9096
9097 RoutePoint *pNearbyPoint =
9098 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9099 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9100 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9101 int dlg_return;
9102#ifndef __WXOSX__
9103 m_FinishRouteOnKillFocus =
9104 false; // Avoid route finish on focus change for message dialog
9105 dlg_return = OCPNMessageBox(
9106 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9107 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9108 m_FinishRouteOnKillFocus = true;
9109#else
9110 dlg_return = wxID_YES;
9111#endif
9112 if (dlg_return == wxID_YES) {
9113 pMousePoint = pNearbyPoint;
9114
9115 // Using existing waypoint, so nothing to delete for undo.
9116 if (m_routeState > 1)
9117 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9118 Undo_HasParent, NULL);
9119 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9120
9121 bool procede = false;
9122 if (tail) {
9123 procede = true;
9124 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9125 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9126 procede = false;
9127 }
9128
9129 if (procede) {
9130 int dlg_return;
9131 m_FinishRouteOnKillFocus = false;
9132 if (m_routeState == 1) { // first point in new route, preceeding
9133 // route to be added? touch case
9134
9135 wxString dmsg =
9136 _("Insert first part of this route in the new route?");
9137 if (tail->GetIndexOf(pMousePoint) ==
9138 tail->GetnPoints()) // Starting on last point of another
9139 // route?
9140 dmsg = _("Insert this route in the new route?");
9141
9142 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9143 dlg_return =
9144 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9145 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9146 m_FinishRouteOnKillFocus = true;
9147
9148 if (dlg_return == wxID_YES) {
9149 inserting = true; // part of the other route will be
9150 // preceeding the new route
9151 }
9152 }
9153 } else {
9154 wxString dmsg =
9155 _("Append last part of this route to the new route?");
9156 if (tail->GetIndexOf(pMousePoint) == 1)
9157 dmsg = _(
9158 "Append this route to the new route?"); // Picking the
9159 // first point of
9160 // another route?
9161
9162 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9163 dlg_return =
9164 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9165 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9166 m_FinishRouteOnKillFocus = true;
9167
9168 if (dlg_return == wxID_YES) {
9169 appending = true; // part of the other route will be
9170 // appended to the new route
9171 }
9172 }
9173 }
9174 }
9175
9176 // check all other routes to see if this point appears in any other
9177 // route If it appears in NO other route, then it should e
9178 // considered an isolated mark
9179 if (!FindRouteContainingWaypoint(pMousePoint))
9180 pMousePoint->SetShared(true);
9181 }
9182 }
9183
9184 if (NULL == pMousePoint) { // need a new point
9185 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9186 "", wxEmptyString);
9187 pMousePoint->SetNameShown(false);
9188
9189 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9190
9191 if (m_routeState > 1)
9192 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9193 Undo_IsOrphanded, NULL);
9194 }
9195
9196 if (m_routeState == 1) {
9197 // First point in the route.
9198 m_pMouseRoute->AddPoint(pMousePoint);
9199 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9200
9201 } else {
9202 if (m_pMouseRoute->m_NextLegGreatCircle) {
9203 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9204 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9205 &rhumbBearing, &rhumbDist);
9206 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9207 &gcDist, &gcBearing, NULL);
9208 double gcDistNM = gcDist / 1852.0;
9209
9210 // Empirically found expression to get reasonable route segments.
9211 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9212 pow(rhumbDist - gcDistNM - 1, 0.5);
9213
9214 wxString msg;
9215 msg << _("For this leg the Great Circle route is ")
9216 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9217 << _(" shorter than rhumbline.\n\n")
9218 << _("Would you like include the Great Circle routing points "
9219 "for this leg?");
9220
9221#ifndef __WXOSX__
9222 m_FinishRouteOnKillFocus = false;
9223 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9224 wxYES_NO | wxNO_DEFAULT);
9225 m_FinishRouteOnKillFocus = true;
9226#else
9227 int answer = wxID_NO;
9228#endif
9229
9230 if (answer == wxID_YES) {
9231 RoutePoint *gcPoint;
9232 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9233 wxRealPoint gcCoord;
9234
9235 for (int i = 1; i <= segmentCount; i++) {
9236 double fraction = (double)i * (1.0 / (double)segmentCount);
9237 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9238 gcDist * fraction, gcBearing,
9239 &gcCoord.x, &gcCoord.y, NULL);
9240
9241 if (i < segmentCount) {
9242 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9243 wxEmptyString);
9244 gcPoint->SetNameShown(false);
9245 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9246 gcPoint);
9247 } else {
9248 gcPoint = pMousePoint; // Last point, previously exsisting!
9249 }
9250
9251 m_pMouseRoute->AddPoint(gcPoint);
9252 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9253
9254 pSelect->AddSelectableRouteSegment(
9255 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9256 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9257 prevGcPoint = gcPoint;
9258 }
9259
9260 undo->CancelUndoableAction(true);
9261
9262 } else {
9263 m_pMouseRoute->AddPoint(pMousePoint);
9264 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9265 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9266 rlon, m_prev_pMousePoint,
9267 pMousePoint, m_pMouseRoute);
9268 undo->AfterUndoableAction(m_pMouseRoute);
9269 }
9270 } else {
9271 // Ordinary rhumblinesegment.
9272 m_pMouseRoute->AddPoint(pMousePoint);
9273 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9274
9275 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9276 rlon, m_prev_pMousePoint,
9277 pMousePoint, m_pMouseRoute);
9278 undo->AfterUndoableAction(m_pMouseRoute);
9279 }
9280 }
9281
9282 m_prev_rlat = rlat;
9283 m_prev_rlon = rlon;
9284 m_prev_pMousePoint = pMousePoint;
9285 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9286
9287 m_routeState++;
9288
9289 if (appending ||
9290 inserting) { // Appending a route or making a new route
9291 int connect = tail->GetIndexOf(pMousePoint);
9292 if (connect == 1) {
9293 inserting = false; // there is nothing to insert
9294 appending = true; // so append
9295 }
9296 int length = tail->GetnPoints();
9297
9298 int i;
9299 int start, stop;
9300 if (appending) {
9301 start = connect + 1;
9302 stop = length;
9303 } else { // inserting
9304 start = 1;
9305 stop = connect;
9306 m_pMouseRoute->RemovePoint(
9307 m_pMouseRoute
9308 ->GetLastPoint()); // Remove the first and only point
9309 }
9310 for (i = start; i <= stop; i++) {
9311 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9312 if (m_pMouseRoute)
9313 m_pMouseRoute->m_lastMousePointIndex =
9314 m_pMouseRoute->GetnPoints();
9315 m_routeState++;
9316 gFrame->RefreshAllCanvas();
9317 ret = true;
9318 }
9319 m_prev_rlat =
9320 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9321 m_prev_rlon =
9322 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9323 m_pMouseRoute->FinalizeForRendering();
9324 }
9325
9326 Refresh(true);
9327 ret = true;
9328 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9329 {
9330 if (m_bedge_pan) {
9331 m_bedge_pan = false;
9332 return false;
9333 }
9334
9335 if (m_nMeasureState == 1) {
9336 m_pMeasureRoute = new Route();
9337 pRouteList->push_back(m_pMeasureRoute);
9338 r_rband.x = x;
9339 r_rband.y = y;
9340 }
9341
9342 if (m_pMeasureRoute) {
9343 RoutePoint *pMousePoint =
9344 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9345 wxEmptyString, wxEmptyString);
9346 pMousePoint->m_bShowName = false;
9347
9348 m_pMeasureRoute->AddPoint(pMousePoint);
9349
9350 m_prev_rlat = m_cursor_lat;
9351 m_prev_rlon = m_cursor_lon;
9352 m_prev_pMousePoint = pMousePoint;
9353 m_pMeasureRoute->m_lastMousePointIndex =
9354 m_pMeasureRoute->GetnPoints();
9355
9356 m_nMeasureState++;
9357 } else {
9358 CancelMeasureRoute();
9359 }
9360
9361 Refresh(true);
9362 ret = true;
9363 } else {
9364 bool bSelectAllowed = true;
9365 if (NULL == g_pMarkInfoDialog) {
9366 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9367 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9368 bSelectAllowed = false;
9369
9370 /*if this left up happens at the end of a route point dragging and if
9371 the cursor/thumb is on the draghandle icon, not on the point iself a new
9372 selection will select nothing and the drag will never be ended, so the
9373 legs around this point never selectable. At this step we don't need a
9374 new selection, just keep the previoulsly selected and dragged point */
9375 if (m_bRoutePoinDragging) bSelectAllowed = false;
9376
9377 if (bSelectAllowed) {
9378 bool b_was_editing_mark = m_bMarkEditing;
9379 bool b_was_editing_route = m_bRouteEditing;
9380 FindRoutePointsAtCursor(SelectRadius,
9381 true); // Possibly selecting a point in a
9382 // route for later dragging
9383
9384 /*route and a mark points in layer can't be dragged so should't be
9385 * selected and no draghandle icon*/
9386 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9387 m_pRoutePointEditTarget = NULL;
9388
9389 if (!b_was_editing_route) {
9390 if (m_pEditRouteArray) {
9391 b_startedit_route = true;
9392
9393 // Hide the track and route rollover during route point edit, not
9394 // needed, and may be confusing
9395 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9396 m_pTrackRolloverWin->IsActive(false);
9397 }
9398 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9399 m_pRouteRolloverWin->IsActive(false);
9400 }
9401
9402 wxRect pre_rect;
9403 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9404 ir++) {
9405 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9406 // Need to validate route pointer
9407 // Route may be gone due to drgging close to ownship with
9408 // "Delete On Arrival" state set, as in the case of
9409 // navigating to an isolated waypoint on a temporary route
9410 if (g_pRouteMan->IsRouteValid(pr)) {
9411 // pr->SetHiLite(50);
9412 wxRect route_rect;
9413 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9414 pre_rect.Union(route_rect);
9415 }
9416 }
9417 RefreshRect(pre_rect, true);
9418 }
9419 } else {
9420 b_startedit_route = false;
9421 }
9422
9423 // Mark editing
9424 if (m_pRoutePointEditTarget) {
9425 if (b_was_editing_mark ||
9426 b_was_editing_route) { // kill previous hilight
9427 if (m_lastRoutePointEditTarget) {
9428 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9429 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9430 RoutePointGui(*m_lastRoutePointEditTarget)
9431 .EnableDragHandle(false);
9432 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9433 SELTYPE_DRAGHANDLE);
9434 }
9435 }
9436
9437 if (m_pRoutePointEditTarget) {
9438 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9439 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9440 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9441 wxPoint2DDouble dragHandlePoint =
9442 RoutePointGui(*m_pRoutePointEditTarget)
9443 .GetDragHandlePoint(this);
9444 pSelect->AddSelectablePoint(
9445 dragHandlePoint.m_y, dragHandlePoint.m_x,
9446 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9447 }
9448 } else { // Deselect everything
9449 if (m_lastRoutePointEditTarget) {
9450 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9451 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9452 RoutePointGui(*m_lastRoutePointEditTarget)
9453 .EnableDragHandle(false);
9454 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9455 SELTYPE_DRAGHANDLE);
9456
9457 // Clear any routes being edited, probably orphans
9458 wxArrayPtrVoid *lastEditRouteArray =
9460 m_lastRoutePointEditTarget);
9461 if (lastEditRouteArray) {
9462 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9463 ir++) {
9464 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9465 if (g_pRouteMan->IsRouteValid(pr)) {
9466 pr->m_bIsBeingEdited = false;
9467 }
9468 }
9469 delete lastEditRouteArray;
9470 }
9471 }
9472 }
9473
9474 // Do the refresh
9475
9476 if (g_bopengl) {
9477 InvalidateGL();
9478 Refresh(false);
9479 } else {
9480 if (m_lastRoutePointEditTarget) {
9481 wxRect wp_rect;
9482 RoutePointGui(*m_lastRoutePointEditTarget)
9483 .CalculateDCRect(m_dc_route, this, &wp_rect);
9484 RefreshRect(wp_rect, true);
9485 }
9486
9487 if (m_pRoutePointEditTarget) {
9488 wxRect wp_rect;
9489 RoutePointGui(*m_pRoutePointEditTarget)
9490 .CalculateDCRect(m_dc_route, this, &wp_rect);
9491 RefreshRect(wp_rect, true);
9492 }
9493 }
9494 }
9495 } // bSelectAllowed
9496
9497 // Check to see if there is a route or AIS target under the cursor
9498 // If so, start the rollover timer which creates the popup
9499 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9500 bool b_start_rollover = false;
9501 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9502 SelectItem *pFind = pSelectAIS->FindSelection(
9503 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9504 if (pFind) b_start_rollover = true;
9505 }
9506
9507 if (!b_start_rollover && !b_startedit_route) {
9508 SelectableItemList SelList = pSelect->FindSelectionList(
9509 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9510 for (SelectItem *pFindSel : SelList) {
9511 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9512 if (pr && pr->IsVisible()) {
9513 b_start_rollover = true;
9514 break;
9515 }
9516 } // while
9517 }
9518
9519 if (!b_start_rollover && !b_startedit_route) {
9520 SelectableItemList SelList = pSelect->FindSelectionList(
9521 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9522 for (SelectItem *pFindSel : SelList) {
9523 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9524 if (tr && tr->IsVisible()) {
9525 b_start_rollover = true;
9526 break;
9527 }
9528 } // while
9529 }
9530
9531 if (b_start_rollover)
9532 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9533 wxTIMER_ONE_SHOT);
9534 Route *tail = 0;
9535 Route *current = 0;
9536 bool appending = false;
9537 bool inserting = false;
9538 int connect = 0;
9539 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9540 // drag
9541 if (m_pRoutePointEditTarget) {
9542 // Check to see if there is a nearby point which may replace the
9543 // dragged one
9544 RoutePoint *pMousePoint = NULL;
9545
9546 int index_last;
9547 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9548 double nearby_radius_meters =
9549 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9550 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9551 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9552 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9553 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9554 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9555 bool duplicate =
9556 false; // ensure we won't create duplicate point in routes
9557 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9558 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9559 ir++) {
9560 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9561 if (pr && pr->pRoutePointList) {
9562 auto *list = pr->pRoutePointList;
9563 auto pos =
9564 std::find(list->begin(), list->end(), pNearbyPoint);
9565 if (pos != list->end()) {
9566 duplicate = true;
9567 break;
9568 }
9569 }
9570 }
9571 }
9572
9573 // Special case:
9574 // Allow "re-use" of a route's waypoints iff it is a simple
9575 // isolated route. This allows, for instance, creation of a closed
9576 // polygon route
9577 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9578
9579 if (!duplicate) {
9580 int dlg_return;
9581 dlg_return =
9582 OCPNMessageBox(this,
9583 _("Replace this RoutePoint by the nearby "
9584 "Waypoint?"),
9585 _("OpenCPN RoutePoint change"),
9586 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9587 if (dlg_return == wxID_YES) {
9588 /*double confirmation if the dragged point has been manually
9589 * created which can be important and could be deleted
9590 * unintentionally*/
9591
9592 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9593 pNearbyPoint);
9594 current =
9595 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9596
9597 if (tail && current && (tail != current)) {
9598 int dlg_return1;
9599 connect = tail->GetIndexOf(pNearbyPoint);
9600 int index_current_route =
9601 current->GetIndexOf(m_pRoutePointEditTarget);
9602 index_last = current->GetIndexOf(current->GetLastPoint());
9603 dlg_return1 = wxID_NO;
9604 if (index_last ==
9605 index_current_route) { // we are dragging the last
9606 // point of the route
9607 if (connect != tail->GetnPoints()) { // anything to do?
9608
9609 wxString dmsg(
9610 _("Last part of route to be appended to dragged "
9611 "route?"));
9612 if (connect == 1)
9613 dmsg =
9614 _("Full route to be appended to dragged route?");
9615
9616 dlg_return1 = OCPNMessageBox(
9617 this, dmsg, _("OpenCPN Route Create"),
9618 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9619 if (dlg_return1 == wxID_YES) {
9620 appending = true;
9621 }
9622 }
9623 } else if (index_current_route ==
9624 1) { // dragging the first point of the route
9625 if (connect != 1) { // anything to do?
9626
9627 wxString dmsg(
9628 _("First part of route to be inserted into dragged "
9629 "route?"));
9630 if (connect == tail->GetnPoints())
9631 dmsg = _(
9632 "Full route to be inserted into dragged route?");
9633
9634 dlg_return1 = OCPNMessageBox(
9635 this, dmsg, _("OpenCPN Route Create"),
9636 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9637 if (dlg_return1 == wxID_YES) {
9638 inserting = true;
9639 }
9640 }
9641 }
9642 }
9643
9644 if (m_pRoutePointEditTarget->IsShared()) {
9645 // dlg_return = wxID_NO;
9646 dlg_return = OCPNMessageBox(
9647 this,
9648 _("Do you really want to delete and replace this "
9649 "WayPoint") +
9650 "\n" + _("which has been created manually?"),
9651 ("OpenCPN RoutePoint warning"),
9652 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9653 }
9654 }
9655 if (dlg_return == wxID_YES) {
9656 pMousePoint = pNearbyPoint;
9657 if (pMousePoint->m_bIsolatedMark) {
9658 pMousePoint->SetShared(true);
9659 }
9660 pMousePoint->m_bIsolatedMark =
9661 false; // definitely no longer isolated
9662 pMousePoint->m_bIsInRoute = true;
9663 }
9664 }
9665 }
9666 }
9667 if (!pMousePoint)
9668 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9669
9670 if (m_pEditRouteArray) {
9671 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9672 ir++) {
9673 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9674 if (g_pRouteMan->IsRouteValid(pr)) {
9675 if (pMousePoint) { // remove the dragged point and insert the
9676 // nearby
9677 auto *list = pr->pRoutePointList;
9678 auto pos = std::find(list->begin(), list->end(),
9679 m_pRoutePointEditTarget);
9680
9681 pSelect->DeleteAllSelectableRoutePoints(pr);
9682 pSelect->DeleteAllSelectableRouteSegments(pr);
9683
9684 pr->pRoutePointList->insert(pos, pMousePoint);
9685 pos = std::find(list->begin(), list->end(),
9686 m_pRoutePointEditTarget);
9687 pr->pRoutePointList->erase(pos);
9688
9689 pSelect->AddAllSelectableRouteSegments(pr);
9690 pSelect->AddAllSelectableRoutePoints(pr);
9691 }
9692 pr->FinalizeForRendering();
9693 pr->UpdateSegmentDistances();
9694 if (m_bRoutePoinDragging) {
9695 // pConfig->UpdateRoute(pr);
9696 NavObj_dB::GetInstance().UpdateRoute(pr);
9697 }
9698 }
9699 }
9700 }
9701
9702 // Update the RouteProperties Dialog, if currently shown
9703 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9704 if (m_pEditRouteArray) {
9705 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9706 ir++) {
9707 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9708 if (g_pRouteMan->IsRouteValid(pr)) {
9709 if (pRoutePropDialog->GetRoute() == pr) {
9710 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9711 }
9712 /* cannot edit track points anyway
9713 else if ( ( NULL !=
9714 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9715 pTrackPropDialog->m_pTrack == pr ) {
9716 pTrackPropDialog->SetTrackAndUpdate(
9717 pr );
9718 }
9719 */
9720 }
9721 }
9722 }
9723 }
9724 if (pMousePoint) { // clear all about the dragged point
9725 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9726 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9727 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9728 // Hide mark properties dialog if open on the replaced point
9729 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9730 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9731 g_pMarkInfoDialog->Hide();
9732
9733 delete m_pRoutePointEditTarget;
9734 m_lastRoutePointEditTarget = NULL;
9735 m_pRoutePointEditTarget = NULL;
9736 undo->AfterUndoableAction(pMousePoint);
9737 undo->InvalidateUndo();
9738 }
9739 }
9740 }
9741
9742 else if (m_bMarkEditing) { // End of way point drag
9743 if (m_pRoutePointEditTarget)
9744 if (m_bRoutePoinDragging) {
9745 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9746 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9747 }
9748 }
9749
9750 if (m_pRoutePointEditTarget)
9751 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9752
9753 if (!m_pRoutePointEditTarget) {
9754 delete m_pEditRouteArray;
9755 m_pEditRouteArray = NULL;
9756 m_bRouteEditing = false;
9757 }
9758 m_bRoutePoinDragging = false;
9759
9760 if (appending) { // Appending to the route of which the last point is
9761 // dragged onto another route
9762
9763 // copy tail from connect until length to end of current after dragging
9764
9765 int length = tail->GetnPoints();
9766 for (int i = connect + 1; i <= length; i++) {
9767 current->AddPointAndSegment(tail->GetPoint(i), false);
9768 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9769 m_routeState++;
9770 gFrame->RefreshAllCanvas();
9771 ret = true;
9772 }
9773 current->FinalizeForRendering();
9774 current->m_bIsBeingEdited = false;
9775 FinishRoute();
9776 g_pRouteMan->DeleteRoute(tail);
9777 }
9778 if (inserting) {
9779 pSelect->DeleteAllSelectableRoutePoints(current);
9780 pSelect->DeleteAllSelectableRouteSegments(current);
9781 for (int i = 1; i < connect; i++) { // numbering in the tail route
9782 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9783 }
9784 pSelect->AddAllSelectableRouteSegments(current);
9785 pSelect->AddAllSelectableRoutePoints(current);
9786 current->FinalizeForRendering();
9787 current->m_bIsBeingEdited = false;
9788 g_pRouteMan->DeleteRoute(tail);
9789 }
9790
9791 // Update the RouteProperties Dialog, if currently shown
9792 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9793 if (m_pEditRouteArray) {
9794 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9795 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9796 if (g_pRouteMan->IsRouteValid(pr)) {
9797 if (pRoutePropDialog->GetRoute() == pr) {
9798 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9799 }
9800 }
9801 }
9802 }
9803 }
9804
9805 } // g_btouch
9806
9807 else { // !g_btouch
9808 if (m_bRouteEditing) { // End of RoutePoint drag
9809 Route *tail = 0;
9810 Route *current = 0;
9811 bool appending = false;
9812 bool inserting = false;
9813 int connect = 0;
9814 int index_last;
9815 if (m_pRoutePointEditTarget) {
9816 m_pRoutePointEditTarget->m_bBlink = false;
9817 // Check to see if there is a nearby point which may replace the
9818 // dragged one
9819 RoutePoint *pMousePoint = NULL;
9820 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9821 double nearby_radius_meters =
9822 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9823 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9824 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9825 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9826 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9827 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9828 bool duplicate = false; // don't create duplicate point in routes
9829 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9830 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9831 ir++) {
9832 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9833 if (pr && pr->pRoutePointList) {
9834 auto *list = pr->pRoutePointList;
9835 auto pos =
9836 std::find(list->begin(), list->end(), pNearbyPoint);
9837 if (pos != list->end()) {
9838 duplicate = true;
9839 break;
9840 }
9841 }
9842 }
9843 }
9844
9845 // Special case:
9846 // Allow "re-use" of a route's waypoints iff it is a simple
9847 // isolated route. This allows, for instance, creation of a closed
9848 // polygon route
9849 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9850
9851 if (!duplicate) {
9852 int dlg_return;
9853 dlg_return =
9854 OCPNMessageBox(this,
9855 _("Replace this RoutePoint by the nearby "
9856 "Waypoint?"),
9857 _("OpenCPN RoutePoint change"),
9858 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9859 if (dlg_return == wxID_YES) {
9860 /*double confirmation if the dragged point has been manually
9861 * created which can be important and could be deleted
9862 * unintentionally*/
9863 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9864 pNearbyPoint);
9865 current =
9866 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9867
9868 if (tail && current && (tail != current)) {
9869 int dlg_return1;
9870 connect = tail->GetIndexOf(pNearbyPoint);
9871 int index_current_route =
9872 current->GetIndexOf(m_pRoutePointEditTarget);
9873 index_last = current->GetIndexOf(current->GetLastPoint());
9874 dlg_return1 = wxID_NO;
9875 if (index_last ==
9876 index_current_route) { // we are dragging the last
9877 // point of the route
9878 if (connect != tail->GetnPoints()) { // anything to do?
9879
9880 wxString dmsg(
9881 _("Last part of route to be appended to dragged "
9882 "route?"));
9883 if (connect == 1)
9884 dmsg =
9885 _("Full route to be appended to dragged route?");
9886
9887 dlg_return1 = OCPNMessageBox(
9888 this, dmsg, _("OpenCPN Route Create"),
9889 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9890 if (dlg_return1 == wxID_YES) {
9891 appending = true;
9892 }
9893 }
9894 } else if (index_current_route ==
9895 1) { // dragging the first point of the route
9896 if (connect != 1) { // anything to do?
9897
9898 wxString dmsg(
9899 _("First part of route to be inserted into dragged "
9900 "route?"));
9901 if (connect == tail->GetnPoints())
9902 dmsg = _(
9903 "Full route to be inserted into dragged route?");
9904
9905 dlg_return1 = OCPNMessageBox(
9906 this, dmsg, _("OpenCPN Route Create"),
9907 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9908 if (dlg_return1 == wxID_YES) {
9909 inserting = true;
9910 }
9911 }
9912 }
9913 }
9914
9915 if (m_pRoutePointEditTarget->IsShared()) {
9916 dlg_return = wxID_NO;
9917 dlg_return = OCPNMessageBox(
9918 this,
9919 _("Do you really want to delete and replace this "
9920 "WayPoint") +
9921 "\n" + _("which has been created manually?"),
9922 ("OpenCPN RoutePoint warning"),
9923 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9924 }
9925 }
9926 if (dlg_return == wxID_YES) {
9927 pMousePoint = pNearbyPoint;
9928 if (pMousePoint->m_bIsolatedMark) {
9929 pMousePoint->SetShared(true);
9930 }
9931 pMousePoint->m_bIsolatedMark =
9932 false; // definitely no longer isolated
9933 pMousePoint->m_bIsInRoute = true;
9934 }
9935 }
9936 }
9937 }
9938 if (!pMousePoint)
9939 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9940
9941 if (m_pEditRouteArray) {
9942 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9943 ir++) {
9944 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9945 if (g_pRouteMan->IsRouteValid(pr)) {
9946 if (pMousePoint) { // replace dragged point by nearby one
9947 auto *list = pr->pRoutePointList;
9948 auto pos = std::find(list->begin(), list->end(),
9949 m_pRoutePointEditTarget);
9950
9951 pSelect->DeleteAllSelectableRoutePoints(pr);
9952 pSelect->DeleteAllSelectableRouteSegments(pr);
9953
9954 pr->pRoutePointList->insert(pos, pMousePoint);
9955 pos = std::find(list->begin(), list->end(),
9956 m_pRoutePointEditTarget);
9957 if (pos != list->end()) list->erase(pos);
9958 // pr->pRoutePointList->erase(pos + 1);
9959
9960 pSelect->AddAllSelectableRouteSegments(pr);
9961 pSelect->AddAllSelectableRoutePoints(pr);
9962 }
9963 pr->FinalizeForRendering();
9964 pr->UpdateSegmentDistances();
9965 pr->m_bIsBeingEdited = false;
9966
9967 if (m_bRoutePoinDragging) {
9968 // Special case optimization.
9969 // Dragging a single point of a route
9970 // without any point additions or re-ordering
9971 if (!pMousePoint)
9972 NavObj_dB::GetInstance().UpdateRoutePoint(
9973 m_pRoutePointEditTarget);
9974 else
9975 NavObj_dB::GetInstance().UpdateRoute(pr);
9976 }
9977 pr->SetHiLite(0);
9978 }
9979 }
9980 Refresh(false);
9981 }
9982
9983 if (appending) {
9984 // copy tail from connect until length to end of current after
9985 // dragging
9986
9987 int length = tail->GetnPoints();
9988 for (int i = connect + 1; i <= length; i++) {
9989 current->AddPointAndSegment(tail->GetPoint(i), false);
9990 if (current)
9991 current->m_lastMousePointIndex = current->GetnPoints();
9992 m_routeState++;
9993 gFrame->RefreshAllCanvas();
9994 ret = true;
9995 }
9996 current->FinalizeForRendering();
9997 current->m_bIsBeingEdited = false;
9998 FinishRoute();
9999 g_pRouteMan->DeleteRoute(tail);
10000 }
10001 if (inserting) {
10002 pSelect->DeleteAllSelectableRoutePoints(current);
10003 pSelect->DeleteAllSelectableRouteSegments(current);
10004 for (int i = 1; i < connect; i++) { // numbering in the tail route
10005 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10006 }
10007 pSelect->AddAllSelectableRouteSegments(current);
10008 pSelect->AddAllSelectableRoutePoints(current);
10009 current->FinalizeForRendering();
10010 current->m_bIsBeingEdited = false;
10011 g_pRouteMan->DeleteRoute(tail);
10012 }
10013
10014 // Update the RouteProperties Dialog, if currently shown
10015 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10016 if (m_pEditRouteArray) {
10017 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10018 ir++) {
10019 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10020 if (g_pRouteMan->IsRouteValid(pr)) {
10021 if (pRoutePropDialog->GetRoute() == pr) {
10022 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10023 }
10024 }
10025 }
10026 }
10027 }
10028
10029 if (pMousePoint) {
10030 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10031 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10032 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10033 // Hide mark properties dialog if open on the replaced point
10034 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10035 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10036 g_pMarkInfoDialog->Hide();
10037
10038 delete m_pRoutePointEditTarget;
10039 m_lastRoutePointEditTarget = NULL;
10040 undo->AfterUndoableAction(pMousePoint);
10041 undo->InvalidateUndo();
10042 } else {
10043 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10044 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10045
10046 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10047 }
10048
10049 delete m_pEditRouteArray;
10050 m_pEditRouteArray = NULL;
10051 }
10052
10053 InvalidateGL();
10054 m_bRouteEditing = false;
10055 m_pRoutePointEditTarget = NULL;
10056
10057 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10058 ret = true;
10059 }
10060
10061 else if (m_bMarkEditing) { // end of Waypoint drag
10062 if (m_pRoutePointEditTarget) {
10063 if (m_bRoutePoinDragging) {
10064 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10065 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10066 }
10067 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10068 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10069 if (!g_bopengl) {
10070 wxRect wp_rect;
10071 RoutePointGui(*m_pRoutePointEditTarget)
10072 .CalculateDCRect(m_dc_route, this, &wp_rect);
10073 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10074 RefreshRect(wp_rect, true);
10075 }
10076 }
10077 m_pRoutePointEditTarget = NULL;
10078 m_bMarkEditing = false;
10079 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10080 ret = true;
10081 }
10082
10083 else if (leftIsDown) { // left click for chart center
10084 leftIsDown = false;
10085 ret = false;
10086
10087 if (!g_btouch) {
10088 if (!m_bChartDragging && !m_bMeasure_Active) {
10089 } else {
10090 m_bChartDragging = false;
10091 }
10092 }
10093 }
10094 m_bRoutePoinDragging = false;
10095 } // !btouch
10096
10097 if (ret) return true;
10098 } // left up
10099
10100 if (event.RightDown()) {
10101 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10102 last_drag.x = mx;
10103 last_drag.y = my;
10104
10105 if (g_btouch) {
10106 // if( m_pRoutePointEditTarget )
10107 // return false;
10108 }
10109
10110 ret = true;
10111 m_FinishRouteOnKillFocus = false;
10112 CallPopupMenu(mx, my);
10113 m_FinishRouteOnKillFocus = true;
10114 } // Right down
10115
10116 return ret;
10117}
10118
10119bool panleftIsDown;
10120
10121bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10122 // Skip all mouse processing if shift is held.
10123 // This allows plugins to implement shift+drag behaviors.
10124 if (event.ShiftDown()) {
10125 return false;
10126 }
10127 int x, y;
10128 event.GetPosition(&x, &y);
10129
10130 x *= m_displayScale;
10131 y *= m_displayScale;
10132
10133 // Check for wheel rotation
10134 // ideally, should be just longer than the time between
10135 // processing accumulated mouse events from the event queue
10136 // as would happen during screen redraws.
10137 int wheel_dir = event.GetWheelRotation();
10138
10139 if (wheel_dir) {
10140 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10141 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10142
10143 double factor = g_mouse_zoom_sensitivity;
10144 if (wheel_dir < 0) factor = 1 / factor;
10145
10146 if (g_bsmoothpanzoom) {
10147 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10148 if (wheel_dir == m_last_wheel_dir) {
10149 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10150 // m_zoom_target /= factor;
10151 } else
10152 StopMovement();
10153 } else {
10154 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10155 m_wheelstopwatch.Start(0);
10156 // m_zoom_target = VPoint.chart_scale / factor;
10157 }
10158 }
10159
10160 m_last_wheel_dir = wheel_dir;
10161
10162 ZoomCanvas(factor, true, false);
10163 }
10164
10165 if (event.LeftDown()) {
10166 // Skip the first left click if it will cause a canvas focus shift
10167 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10168 return false;
10169 }
10170
10171 last_drag.x = x, last_drag.y = y;
10172 panleftIsDown = true;
10173 }
10174
10175 if (event.LeftUp()) {
10176 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10177 // seen here.
10178 panleftIsDown = false;
10179
10180 if (!g_btouch) {
10181 if (!m_bChartDragging && !m_bMeasure_Active) {
10182 switch (cursor_region) {
10183 case MID_RIGHT: {
10184 PanCanvas(100, 0);
10185 break;
10186 }
10187
10188 case MID_LEFT: {
10189 PanCanvas(-100, 0);
10190 break;
10191 }
10192
10193 case MID_TOP: {
10194 PanCanvas(0, 100);
10195 break;
10196 }
10197
10198 case MID_BOT: {
10199 PanCanvas(0, -100);
10200 break;
10201 }
10202
10203 case CENTER: {
10204 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10205 break;
10206 }
10207 }
10208 } else {
10209 m_bChartDragging = false;
10210 }
10211 }
10212 }
10213 }
10214
10215 if (event.Dragging() && event.LeftIsDown()) {
10216 /*
10217 * fixed dragging.
10218 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10219 * before the drag event. Hence, as there is no mouse down event, last_drag
10220 * is not reset before the drag. And that results in one single drag
10221 * session, meaning you cannot drag the map a few miles north, lift your
10222 * finger, and the go even further north. Instead, the map resets itself
10223 * always to the very first drag start (since there is not reset of
10224 * last_drag).
10225 *
10226 * Besides, should not left down and dragging be enough of a situation to
10227 * start a drag procedure?
10228 *
10229 * Anyways, guarded it to be active in touch situations only.
10230 */
10231
10232 if (g_btouch && !m_inPinch) {
10233 struct timespec now;
10234 clock_gettime(CLOCK_MONOTONIC, &now);
10235 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10236
10237 if (false == m_bChartDragging) {
10238 // Reset drag calculation members
10239 last_drag.x = x, last_drag.y = y;
10240 m_bChartDragging = true;
10241 m_chart_drag_total_time = 0;
10242 m_chart_drag_total_x = 0;
10243 m_chart_drag_total_y = 0;
10244 m_inertia_last_drag_x = x;
10245 m_inertia_last_drag_y = y;
10246 m_drag_vec_x.clear();
10247 m_drag_vec_y.clear();
10248 m_drag_vec_t.clear();
10249 m_last_drag_time = tnow;
10250 }
10251
10252 // Calculate and store drag dynamics.
10253 uint64_t delta_t = tnow - m_last_drag_time;
10254 double delta_tf = delta_t / 1e9;
10255
10256 m_chart_drag_total_time += delta_tf;
10257 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10258 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10259
10260 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10261 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10262 m_drag_vec_t.push_back(delta_tf);
10263
10264 m_inertia_last_drag_x = x;
10265 m_inertia_last_drag_y = y;
10266 m_last_drag_time = tnow;
10267
10268 if ((last_drag.x != x) || (last_drag.y != y)) {
10269 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10270 // dragging on route create.
10271 // github #2994
10272 m_bChartDragging = true;
10273 StartTimedMovement();
10274 m_pan_drag.x += last_drag.x - x;
10275 m_pan_drag.y += last_drag.y - y;
10276 last_drag.x = x, last_drag.y = y;
10277 }
10278 }
10279 } else if (!g_btouch) {
10280 if ((last_drag.x != x) || (last_drag.y != y)) {
10281 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10282 // dragging on route create.
10283 // github #2994
10284 m_bChartDragging = true;
10285 StartTimedMovement();
10286 m_pan_drag.x += last_drag.x - x;
10287 m_pan_drag.y += last_drag.y - y;
10288 last_drag.x = x, last_drag.y = y;
10289 }
10290 }
10291 }
10292
10293 // Handle some special cases
10294 if (g_btouch) {
10295 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10296 // deactivate next LeftUp to ovoid creating an unexpected point
10297 m_DoubleClickTimer->Start();
10298 singleClickEventIsValid = false;
10299 }
10300 }
10301 }
10302
10303 return true;
10304}
10305
10306void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10307 if (MouseEventOverlayWindows(event)) return;
10308
10309 if (MouseEventSetup(event)) return; // handled, no further action required
10310
10311 if (!MouseEventProcessObjects(event)) MouseEventProcessCanvas(event);
10312}
10313
10314void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10315 // Switch to the appropriate cursor on mouse movement
10316
10317 wxCursor *ptarget_cursor = pCursorArrow;
10318 if (!pPlugIn_Cursor) {
10319 ptarget_cursor = pCursorArrow;
10320 if ((!m_routeState) &&
10321 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10322 if (cursor_region == MID_RIGHT) {
10323 ptarget_cursor = pCursorRight;
10324 } else if (cursor_region == MID_LEFT) {
10325 ptarget_cursor = pCursorLeft;
10326 } else if (cursor_region == MID_TOP) {
10327 ptarget_cursor = pCursorDown;
10328 } else if (cursor_region == MID_BOT) {
10329 ptarget_cursor = pCursorUp;
10330 } else {
10331 ptarget_cursor = pCursorArrow;
10332 }
10333 } else if (m_bMeasure_Active ||
10334 m_routeState) // If Measure tool use Pencil Cursor
10335 ptarget_cursor = pCursorPencil;
10336 } else {
10337 ptarget_cursor = pPlugIn_Cursor;
10338 }
10339
10340 SetCursor(*ptarget_cursor);
10341}
10342
10343void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10344 SetCursor(*pCursorArrow);
10345}
10346
10347void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10348 ChartPlugInWrapper *target_plugin_chart = NULL;
10349 s57chart *Chs57 = NULL;
10350 wxFileName file;
10351 wxArrayString files;
10352
10353 ChartBase *target_chart = GetChartAtCursor();
10354 if (target_chart) {
10355 file.Assign(target_chart->GetFullPath());
10356 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10357 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10358 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10359 else
10360 Chs57 = dynamic_cast<s57chart *>(target_chart);
10361 } else { // target_chart = null, might be mbtiles
10362 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10363 unsigned int im = stackIndexArray.size();
10364 int scale = 2147483647; // max 32b integer
10365 if (VPoint.b_quilt && im > 0) {
10366 for (unsigned int is = 0; is < im; is++) {
10367 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10368 CHART_TYPE_MBTILES) {
10369 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10370 double lat, lon;
10371 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10372 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10373 .GetBBox()
10374 .Contains(lat, lon)) {
10375 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10376 scale) {
10377 scale =
10378 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10379 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10380 }
10381 }
10382 }
10383 }
10384 }
10385 }
10386
10387 std::vector<Ais8_001_22 *> area_notices;
10388
10389 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10390 float vp_scale = GetVPScale();
10391
10392 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10393 auto target_data = target.second;
10394 if (!target_data->area_notices.empty()) {
10395 for (auto &ani : target_data->area_notices) {
10396 Ais8_001_22 &area_notice = ani.second;
10397
10398 BoundingBox bbox;
10399
10400 for (Ais8_001_22_SubAreaList::iterator sa =
10401 area_notice.sub_areas.begin();
10402 sa != area_notice.sub_areas.end(); ++sa) {
10403 switch (sa->shape) {
10404 case AIS8_001_22_SHAPE_CIRCLE: {
10405 wxPoint target_point;
10406 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10407 bbox.Expand(target_point);
10408 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10409 break;
10410 }
10411 case AIS8_001_22_SHAPE_RECT: {
10412 wxPoint target_point;
10413 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10414 bbox.Expand(target_point);
10415 if (sa->e_dim_m > sa->n_dim_m)
10416 bbox.EnLarge(sa->e_dim_m * vp_scale);
10417 else
10418 bbox.EnLarge(sa->n_dim_m * vp_scale);
10419 break;
10420 }
10421 case AIS8_001_22_SHAPE_POLYGON:
10422 case AIS8_001_22_SHAPE_POLYLINE: {
10423 for (int i = 0; i < 4; ++i) {
10424 double lat = sa->latitude;
10425 double lon = sa->longitude;
10426 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10427 &lat, &lon);
10428 wxPoint target_point;
10429 GetCanvasPointPix(lat, lon, &target_point);
10430 bbox.Expand(target_point);
10431 }
10432 break;
10433 }
10434 case AIS8_001_22_SHAPE_SECTOR: {
10435 double lat1 = sa->latitude;
10436 double lon1 = sa->longitude;
10437 double lat, lon;
10438 wxPoint target_point;
10439 GetCanvasPointPix(lat1, lon1, &target_point);
10440 bbox.Expand(target_point);
10441 for (int i = 0; i < 18; ++i) {
10442 ll_gc_ll(
10443 lat1, lon1,
10444 sa->left_bound_deg +
10445 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10446 sa->radius_m / 1852.0, &lat, &lon);
10447 GetCanvasPointPix(lat, lon, &target_point);
10448 bbox.Expand(target_point);
10449 }
10450 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10451 &lat, &lon);
10452 GetCanvasPointPix(lat, lon, &target_point);
10453 bbox.Expand(target_point);
10454 break;
10455 }
10456 }
10457 }
10458
10459 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10460 area_notices.push_back(&area_notice);
10461 }
10462 }
10463 }
10464 }
10465 }
10466
10467 if (target_chart || !area_notices.empty() || file.HasName()) {
10468 // Go get the array of all objects at the cursor lat/lon
10469 int sel_rad_pix = 5;
10470 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10471
10472 // Make sure we always get the lights from an object, even if we are
10473 // currently not displaying lights on the chart.
10474
10475 SetCursor(wxCURSOR_WAIT);
10476 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10477 if (!lightsVis) SetShowENCLights(true);
10478 ;
10479
10480 ListOfObjRazRules *rule_list = NULL;
10481 ListOfPI_S57Obj *pi_rule_list = NULL;
10482 if (Chs57)
10483 rule_list =
10484 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10485 else if (target_plugin_chart)
10486 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10487 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10488
10489 ListOfObjRazRules *overlay_rule_list = NULL;
10490 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10491 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10492
10493 if (CHs57_Overlay) {
10494 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10495 zlat, zlon, SelectRadius, &GetVP());
10496 }
10497
10498 if (!lightsVis) SetShowENCLights(false);
10499
10500 wxString objText;
10501 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10502 wxString face = dFont->GetFaceName();
10503
10504 if (NULL == g_pObjectQueryDialog) {
10506 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10507 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10508 }
10509
10510 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10511 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10512
10513#ifdef __WXOSX__
10514 // Auto Adjustment for dark mode
10515 fg = g_pObjectQueryDialog->GetForegroundColour();
10516#endif
10517
10518 objText.Printf(
10519 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10520 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10521
10522#ifdef __WXOSX__
10523 int points = dFont->GetPointSize();
10524#else
10525 int points = dFont->GetPointSize() + 1;
10526#endif
10527
10528 int sizes[7];
10529 for (int i = -2; i < 5; i++) {
10530 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10531 }
10532 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10533
10534 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10535
10536 if (overlay_rule_list && CHs57_Overlay) {
10537 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10538 objText << "<hr noshade>";
10539 }
10540
10541 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10542 an != area_notices.end(); ++an) {
10543 objText << "<b>AIS Area Notice:</b> ";
10544 objText << ais8_001_22_notice_names[(*an)->notice_type];
10545 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10546 (*an)->sub_areas.begin();
10547 sa != (*an)->sub_areas.end(); ++sa)
10548 if (!sa->text.empty()) objText << sa->text;
10549 objText << "<br>expires: " << (*an)->expiry_time.Format();
10550 objText << "<hr noshade>";
10551 }
10552
10553 if (Chs57)
10554 objText << Chs57->CreateObjDescriptions(rule_list);
10555 else if (target_plugin_chart)
10556 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10557 pi_rule_list);
10558
10559 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10560
10561 // Add the additional info files
10562 wxString AddFiles, filenameOK;
10563 int filecount = 0;
10564 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10565 // plugin
10566
10567 AddFiles = wxString::Format(
10568 "<hr noshade><br><b>Additional info files attached to: </b> "
10569 "<font "
10570 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10571 "cellpadding=3>",
10572 file.GetFullName());
10573 file.Normalize();
10574 file.Assign(file.GetPath(), "");
10575 wxDir dir(file.GetFullPath());
10576 wxString filename;
10577 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10578 while (cont) {
10579 file.Assign(dir.GetNameWithSep().append(filename));
10580 wxString FormatString =
10581 "<td valign=top><font size=-2><a "
10582 "href=\"%s\">%s</a></font></td>";
10583 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10584 filenameOK = file.GetFullPath(); // remember last valid name
10585 // we are making a 3 columns table. New row only every third file
10586 if (3 * ((int)filecount / 3) == filecount)
10587 FormatString.Prepend("<tr>"); // new row
10588 else
10589 FormatString.Prepend(
10590 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10591 // spacer column
10592
10593 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10594 file.GetFullName());
10595 filecount++;
10596 }
10597 cont = dir.GetNext(&filename);
10598 }
10599 objText << AddFiles << "</table>";
10600 }
10601 objText << "</font>";
10602 objText << "</body></html>";
10603
10604 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10605 g_pObjectQueryDialog->SetHTMLPage(objText);
10606 g_pObjectQueryDialog->Show();
10607 }
10608 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10609 // generate an event to avoid double code
10610 wxHtmlLinkInfo hli(filenameOK);
10611 wxHtmlLinkEvent hle(1, hli);
10612 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10613 }
10614
10615 if (rule_list) rule_list->Clear();
10616 delete rule_list;
10617
10618 if (overlay_rule_list) overlay_rule_list->Clear();
10619 delete overlay_rule_list;
10620
10621 if (pi_rule_list) pi_rule_list->Clear();
10622 delete pi_rule_list;
10623
10624 SetCursor(wxCURSOR_ARROW);
10625 }
10626}
10627
10628void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10629 bool bNew = false;
10630 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10631 // Dialog
10632 g_pMarkInfoDialog = new MarkInfoDlg(this);
10633 bNew = true;
10634 }
10635
10636 if (1 /*g_bresponsive*/) {
10637 wxSize canvas_size = GetSize();
10638
10639 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10640 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10641
10642 g_pMarkInfoDialog->Layout();
10643
10644 wxPoint canvas_pos = GetPosition();
10645 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10646
10647 bool newFit = false;
10648 if (canvas_size.x < fitted_size.x) {
10649 fitted_size.x = canvas_size.x - 40;
10650 if (canvas_size.y < fitted_size.y)
10651 fitted_size.y -= 40; // scrollbar added
10652 }
10653 if (canvas_size.y < fitted_size.y) {
10654 fitted_size.y = canvas_size.y - 40;
10655 if (canvas_size.x < fitted_size.x)
10656 fitted_size.x -= 40; // scrollbar added
10657 }
10658
10659 if (newFit) {
10660 g_pMarkInfoDialog->SetSize(fitted_size);
10661 g_pMarkInfoDialog->Centre();
10662 }
10663 }
10664
10665 markPoint->m_bRPIsBeingEdited = false;
10666
10667 wxString title_base = _("Mark Properties");
10668 if (markPoint->m_bIsInRoute) {
10669 title_base = _("Waypoint Properties");
10670 }
10671 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10672 g_pMarkInfoDialog->UpdateProperties();
10673 if (markPoint->m_bIsInLayer) {
10674 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10675 GetLayerName(markPoint->m_LayerID)));
10676 g_pMarkInfoDialog->SetDialogTitle(caption);
10677 } else
10678 g_pMarkInfoDialog->SetDialogTitle(title_base);
10679
10680 g_pMarkInfoDialog->Show();
10681 g_pMarkInfoDialog->Raise();
10682 g_pMarkInfoDialog->InitialFocus();
10683 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10684}
10685
10686void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10687 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10688 pRoutePropDialog->SetRouteAndUpdate(selected);
10689 // pNew->UpdateProperties();
10690 pRoutePropDialog->Show();
10691 pRoutePropDialog->Raise();
10692 return;
10693 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10694 this); // There is one global instance of the RouteProp Dialog
10695
10696 if (g_bresponsive) {
10697 wxSize canvas_size = GetSize();
10698 wxPoint canvas_pos = GetPosition();
10699 wxSize fitted_size = pRoutePropDialog->GetSize();
10700 ;
10701
10702 if (canvas_size.x < fitted_size.x) {
10703 fitted_size.x = canvas_size.x;
10704 if (canvas_size.y < fitted_size.y)
10705 fitted_size.y -= 20; // scrollbar added
10706 }
10707 if (canvas_size.y < fitted_size.y) {
10708 fitted_size.y = canvas_size.y;
10709 if (canvas_size.x < fitted_size.x)
10710 fitted_size.x -= 20; // scrollbar added
10711 }
10712
10713 pRoutePropDialog->SetSize(fitted_size);
10714 pRoutePropDialog->Centre();
10715
10716 // int xp = (canvas_size.x - fitted_size.x)/2;
10717 // int yp = (canvas_size.y - fitted_size.y)/2;
10718
10719 wxPoint xxp = ClientToScreen(canvas_pos);
10720 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10721 }
10722
10723 pRoutePropDialog->SetRouteAndUpdate(selected);
10724
10725 pRoutePropDialog->Show();
10726
10727 Refresh(false);
10728}
10729
10730void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10731 pTrackPropDialog = TrackPropDlg::getInstance(
10732 this); // There is one global instance of the RouteProp Dialog
10733
10734 pTrackPropDialog->SetTrackAndUpdate(selected);
10736
10737 pTrackPropDialog->Show();
10738
10739 Refresh(false);
10740}
10741
10742void pupHandler_PasteWaypoint() {
10743 Kml kml;
10744
10745 int pasteBuffer = kml.ParsePasteBuffer();
10746 RoutePoint *pasted = kml.GetParsedRoutePoint();
10747 if (!pasted) return;
10748
10749 double nearby_radius_meters =
10750 g_Platform->GetSelectRadiusPix() /
10751 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10752
10753 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10754 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10755
10756 int answer = wxID_NO;
10757 if (nearPoint && !nearPoint->m_bIsInLayer) {
10758 wxString msg;
10759 msg << _(
10760 "There is an existing waypoint at the same location as the one you are "
10761 "pasting. Would you like to merge the pasted data with it?\n\n");
10762 msg << _("Answering 'No' will create a new waypoint at the same location.");
10763 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10764 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10765 }
10766
10767 if (answer == wxID_YES) {
10768 nearPoint->SetName(pasted->GetName());
10769 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10770 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10771 pRouteManagerDialog->UpdateWptListCtrl();
10772 }
10773
10774 if (answer == wxID_NO) {
10775 RoutePoint *newPoint = new RoutePoint(pasted);
10776 newPoint->m_bIsolatedMark = true;
10777 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10778 newPoint);
10779 // pConfig->AddNewWayPoint(newPoint, -1);
10780 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10781
10782 pWayPointMan->AddRoutePoint(newPoint);
10783 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10784 pRouteManagerDialog->UpdateWptListCtrl();
10785 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10786 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10787 }
10788
10789 gFrame->InvalidateAllGL();
10790 gFrame->RefreshAllCanvas(false);
10791}
10792
10793void pupHandler_PasteRoute() {
10794 Kml kml;
10795
10796 int pasteBuffer = kml.ParsePasteBuffer();
10797 Route *pasted = kml.GetParsedRoute();
10798 if (!pasted) return;
10799
10800 double nearby_radius_meters =
10801 g_Platform->GetSelectRadiusPix() /
10802 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10803
10804 RoutePoint *curPoint;
10805 RoutePoint *nearPoint;
10806 RoutePoint *prevPoint = NULL;
10807
10808 bool mergepoints = false;
10809 bool createNewRoute = true;
10810 int existingWaypointCounter = 0;
10811
10812 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10813 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10814 nearPoint = pWayPointMan->GetNearbyWaypoint(
10815 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10816 if (nearPoint) {
10817 mergepoints = true;
10818 existingWaypointCounter++;
10819 // Small hack here to avoid both extending RoutePoint and repeating all
10820 // the GetNearbyWaypoint calculations. Use existin data field in
10821 // RoutePoint as temporary storage.
10822 curPoint->m_bPtIsSelected = true;
10823 }
10824 }
10825
10826 int answer = wxID_NO;
10827 if (mergepoints) {
10828 wxString msg;
10829 msg << _(
10830 "There are existing waypoints at the same location as some of the ones "
10831 "you are pasting. Would you like to just merge the pasted data into "
10832 "them?\n\n");
10833 msg << _("Answering 'No' will create all new waypoints for this route.");
10834 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10835 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10836
10837 if (answer == wxID_CANCEL) {
10838 return;
10839 }
10840 }
10841
10842 // If all waypoints exist since before, and a route with the same name, we
10843 // don't create a new route.
10844 if (mergepoints && answer == wxID_YES &&
10845 existingWaypointCounter == pasted->GetnPoints()) {
10846 for (Route *proute : *pRouteList) {
10847 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10848 createNewRoute = false;
10849 break;
10850 }
10851 }
10852 }
10853
10854 Route *newRoute = 0;
10855 RoutePoint *newPoint = 0;
10856
10857 if (createNewRoute) {
10858 newRoute = new Route();
10859 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10860 }
10861
10862 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10863 curPoint = pasted->GetPoint(i);
10864 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10865 curPoint->m_bPtIsSelected = false;
10866 newPoint = pWayPointMan->GetNearbyWaypoint(
10867 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10868 newPoint->SetName(curPoint->GetName());
10869 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10870
10871 if (createNewRoute) newRoute->AddPoint(newPoint);
10872 } else {
10873 curPoint->m_bPtIsSelected = false;
10874
10875 newPoint = new RoutePoint(curPoint);
10876 newPoint->m_bIsolatedMark = false;
10877 newPoint->SetIconName("circle");
10878 newPoint->m_bIsVisible = true;
10879 newPoint->m_bShowName = false;
10880 newPoint->SetShared(false);
10881
10882 newRoute->AddPoint(newPoint);
10883 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10884 newPoint);
10885 // pConfig->AddNewWayPoint(newPoint, -1);
10886 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10887 pWayPointMan->AddRoutePoint(newPoint);
10888 }
10889 if (i > 1 && createNewRoute)
10890 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10891 curPoint->m_lat, curPoint->m_lon,
10892 prevPoint, newPoint, newRoute);
10893 prevPoint = newPoint;
10894 }
10895
10896 if (createNewRoute) {
10897 pRouteList->push_back(newRoute);
10898 // pConfig->AddNewRoute(newRoute); // use auto next num
10899 NavObj_dB::GetInstance().InsertRoute(newRoute);
10900
10901 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10902 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10903 }
10904
10905 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10906 pRouteManagerDialog->UpdateRouteListCtrl();
10907 pRouteManagerDialog->UpdateWptListCtrl();
10908 }
10909 gFrame->InvalidateAllGL();
10910 gFrame->RefreshAllCanvas(false);
10911 }
10912 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10913 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10914}
10915
10916void pupHandler_PasteTrack() {
10917 Kml kml;
10918
10919 int pasteBuffer = kml.ParsePasteBuffer();
10920 Track *pasted = kml.GetParsedTrack();
10921 if (!pasted) return;
10922
10923 TrackPoint *curPoint;
10924
10925 Track *newTrack = new Track();
10926 TrackPoint *newPoint;
10927 TrackPoint *prevPoint = NULL;
10928
10929 newTrack->SetName(pasted->GetName());
10930
10931 for (int i = 0; i < pasted->GetnPoints(); i++) {
10932 curPoint = pasted->GetPoint(i);
10933
10934 newPoint = new TrackPoint(curPoint);
10935
10936 wxDateTime now = wxDateTime::Now();
10937 newPoint->SetCreateTime(curPoint->GetCreateTime());
10938
10939 newTrack->AddPoint(newPoint);
10940
10941 if (prevPoint)
10942 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10943 newPoint->m_lat, newPoint->m_lon,
10944 prevPoint, newPoint, newTrack);
10945
10946 prevPoint = newPoint;
10947 }
10948
10949 g_TrackList.push_back(newTrack);
10950 // pConfig->AddNewTrack(newTrack);
10951 NavObj_dB::GetInstance().InsertTrack(newTrack);
10952
10953 gFrame->InvalidateAllGL();
10954 gFrame->RefreshAllCanvas(false);
10955}
10956
10957bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
10958 wxJSONValue v;
10959 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
10960 v["CursorPosition_x"] = x;
10961 v["CursorPosition_y"] = y;
10962 // Send a limited set of selection types depending on what is
10963 // found under the mouse point.
10964 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
10965 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
10966 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
10967
10968 wxJSONWriter w;
10969 wxString out;
10970 w.Write(v, out);
10971 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
10972
10973 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
10974
10975#if 0
10976#define SELTYPE_UNKNOWN 0x0001
10977#define SELTYPE_ROUTEPOINT 0x0002
10978#define SELTYPE_ROUTESEGMENT 0x0004
10979#define SELTYPE_TIDEPOINT 0x0008
10980#define SELTYPE_CURRENTPOINT 0x0010
10981#define SELTYPE_ROUTECREATE 0x0020
10982#define SELTYPE_AISTARGET 0x0040
10983#define SELTYPE_MARKPOINT 0x0080
10984#define SELTYPE_TRACKSEGMENT 0x0100
10985#define SELTYPE_DRAGHANDLE 0x0200
10986#endif
10987
10988 if (g_bhide_context_menus) return true;
10989 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
10990 m_pFoundRoutePoint, m_FoundAIS_MMSI,
10991 m_pIDXCandidate, m_nmea_log);
10992
10993 Connect(
10994 wxEVT_COMMAND_MENU_SELECTED,
10995 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10996
10997#ifdef __WXGTK__
10998 // Funny requirement here for gtk, to clear the menu trigger event
10999 // TODO
11000 // Causes a slight "flasH" of the menu,
11001 if (m_inLongPress) {
11002 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11003 m_inLongPress = false;
11004 }
11005#endif
11006
11007 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11008
11009 Disconnect(
11010 wxEVT_COMMAND_MENU_SELECTED,
11011 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11012
11013 delete m_canvasMenu;
11014 m_canvasMenu = NULL;
11015
11016#ifdef __WXQT__
11017 // gFrame->SurfaceToolbar();
11018 // g_MainToolbar->Raise();
11019#endif
11020
11021 return true;
11022}
11023
11024void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11025 // Pass menu events from the canvas to the menu handler
11026 // This is necessarily in ChartCanvas since that is the menu's parent.
11027 if (m_canvasMenu) {
11028 m_canvasMenu->PopupMenuHandler(event);
11029 }
11030 return;
11031}
11032
11033void ChartCanvas::StartRoute() {
11034 // Do not allow more than one canvas to create a route at one time.
11035 if (g_brouteCreating) return;
11036
11037 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11038
11039 g_brouteCreating = true;
11040 m_routeState = 1;
11041 m_bDrawingRoute = false;
11042 SetCursor(*pCursorPencil);
11043 // SetCanvasToolbarItemState(ID_ROUTE, true);
11044 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11045
11046 HideGlobalToolbar();
11047
11048#ifdef __ANDROID__
11049 androidSetRouteAnnunciator(true);
11050#endif
11051}
11052
11053wxString ChartCanvas::FinishRoute() {
11054 m_routeState = 0;
11055 m_prev_pMousePoint = NULL;
11056 m_bDrawingRoute = false;
11057 wxString rv = "";
11058 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11059
11060 // SetCanvasToolbarItemState(ID_ROUTE, false);
11061 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11062#ifdef __ANDROID__
11063 androidSetRouteAnnunciator(false);
11064#endif
11065
11066 SetCursor(*pCursorArrow);
11067
11068 if (m_pMouseRoute) {
11069 if (m_bAppendingRoute) {
11070 // pConfig->UpdateRoute(m_pMouseRoute);
11071 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11072 } else {
11073 if (m_pMouseRoute->GetnPoints() > 1) {
11074 // pConfig->AddNewRoute(m_pMouseRoute);
11075 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11076 } else {
11077 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11078 m_pMouseRoute = NULL;
11079 }
11080 }
11081 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11082
11083 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11084 (pRoutePropDialog->IsShown())) {
11085 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11086 }
11087
11088 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11089 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11090 pRouteManagerDialog->UpdateRouteListCtrl();
11091 }
11092 }
11093 m_bAppendingRoute = false;
11094 m_pMouseRoute = NULL;
11095
11096 m_pSelectedRoute = NULL;
11097
11098 undo->InvalidateUndo();
11099 gFrame->RefreshAllCanvas(true);
11100
11101 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11102
11103 ShowGlobalToolbar();
11104
11105 g_brouteCreating = false;
11106
11107 return rv;
11108}
11109
11110void ChartCanvas::HideGlobalToolbar() {
11111 if (m_canvasIndex == 0) {
11112 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
11113 }
11114}
11115
11116void ChartCanvas::ShowGlobalToolbar() {
11117 if (m_canvasIndex == 0) {
11118 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
11119 }
11120}
11121
11122void ChartCanvas::ShowAISTargetList() {
11123 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11124 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11125 }
11126
11127 g_pAISTargetList->UpdateAISTargetList();
11128}
11129
11130void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11131 if (!m_bShowOutlines) return;
11132
11133 if (!ChartData) return;
11134
11135 int nEntry = ChartData->GetChartTableEntries();
11136
11137 for (int i = 0; i < nEntry; i++) {
11138 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11139
11140 // Check to see if the candidate chart is in the currently active group
11141 bool b_group_draw = false;
11142 if (m_groupIndex > 0) {
11143 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11144 int index = pt->GetGroupArray()[ig];
11145 if (m_groupIndex == index) {
11146 b_group_draw = true;
11147 break;
11148 }
11149 }
11150 } else
11151 b_group_draw = true;
11152
11153 if (b_group_draw) RenderChartOutline(dc, i, vp);
11154 }
11155
11156 // On CM93 Composite Charts, draw the outlines of the next smaller
11157 // scale cell
11158 cm93compchart *pcm93 = NULL;
11159 if (VPoint.b_quilt) {
11160 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11161 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11162 pcm93 = (cm93compchart *)pch;
11163 break;
11164 }
11165 } else if (m_singleChart &&
11166 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11167 pcm93 = (cm93compchart *)m_singleChart;
11168
11169 if (pcm93) {
11170 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11171 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11172
11173 if (zoom_factor > 8.0) {
11174 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11175 dc.SetPen(mPen);
11176 } else {
11177 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11178 dc.SetPen(mPen);
11179 }
11180
11181 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11182 }
11183}
11184
11185void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11186#ifdef ocpnUSE_GL
11187 if (g_bopengl && m_glcc) {
11188 /* opengl version specially optimized */
11189 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11190 return;
11191 }
11192#endif
11193
11194 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11195 if (!ChartData->IsChartAvailable(dbIndex)) return;
11196 }
11197
11198 float plylat, plylon;
11199 float plylat1, plylon1;
11200
11201 int pixx, pixy, pixx1, pixy1;
11202
11203 LLBBox box;
11204 ChartData->GetDBBoundingBox(dbIndex, box);
11205
11206 // Don't draw an outline in the case where the chart covers the entire world
11207 // */
11208 if (box.GetLonRange() == 360) return;
11209
11210 double lon_bias = 0;
11211 // chart is outside of viewport lat/lon bounding box
11212 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11213
11214 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11215
11216 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11217 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11218
11219 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11220 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11221
11222 else
11223 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11224
11225 // Are there any aux ply entries?
11226 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11227 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11228 {
11229 wxPoint r, r1;
11230
11231 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11232 plylon += lon_bias;
11233
11234 GetCanvasPointPix(plylat, plylon, &r);
11235 pixx = r.x;
11236 pixy = r.y;
11237
11238 for (int i = 0; i < nPly - 1; i++) {
11239 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11240 plylon1 += lon_bias;
11241
11242 GetCanvasPointPix(plylat1, plylon1, &r1);
11243 pixx1 = r1.x;
11244 pixy1 = r1.y;
11245
11246 int pixxs1 = pixx1;
11247 int pixys1 = pixy1;
11248
11249 bool b_skip = false;
11250
11251 if (vp.chart_scale > 5e7) {
11252 // calculate projected distance between these two points in meters
11253 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11254 pow((double)(pixy1 - pixy), 2)) /
11255 vp.view_scale_ppm;
11256
11257 if (dist > 0.0) {
11258 // calculate GC distance between these two points in meters
11259 double distgc =
11260 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11261
11262 // If the distances are nonsense, it means that the scale is very
11263 // small and the segment wrapped the world So skip it....
11264 // TODO improve this to draw two segments
11265 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11266 b_skip = true;
11267 } else
11268 b_skip = true;
11269 }
11270
11271 ClipResult res = cohen_sutherland_line_clip_i(
11272 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11273 if (res != Invisible && !b_skip)
11274 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11275
11276 plylat = plylat1;
11277 plylon = plylon1;
11278 pixx = pixxs1;
11279 pixy = pixys1;
11280 }
11281
11282 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11283 plylon1 += lon_bias;
11284
11285 GetCanvasPointPix(plylat1, plylon1, &r1);
11286 pixx1 = r1.x;
11287 pixy1 = r1.y;
11288
11289 ClipResult res = cohen_sutherland_line_clip_i(
11290 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11291 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11292 }
11293
11294 else // Use Aux PlyPoints
11295 {
11296 wxPoint r, r1;
11297
11298 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11299 for (int j = 0; j < nAuxPlyEntries; j++) {
11300 int nAuxPly =
11301 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11302 GetCanvasPointPix(plylat, plylon, &r);
11303 pixx = r.x;
11304 pixy = r.y;
11305
11306 for (int i = 0; i < nAuxPly - 1; i++) {
11307 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11308
11309 GetCanvasPointPix(plylat1, plylon1, &r1);
11310 pixx1 = r1.x;
11311 pixy1 = r1.y;
11312
11313 int pixxs1 = pixx1;
11314 int pixys1 = pixy1;
11315
11316 bool b_skip = false;
11317
11318 if (vp.chart_scale > 5e7) {
11319 // calculate projected distance between these two points in meters
11320 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11321 ((pixy1 - pixy) * (pixy1 - pixy))) /
11322 vp.view_scale_ppm;
11323 if (dist > 0.0) {
11324 // calculate GC distance between these two points in meters
11325 double distgc =
11326 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11327
11328 // If the distances are nonsense, it means that the scale is very
11329 // small and the segment wrapped the world So skip it....
11330 // TODO improve this to draw two segments
11331 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11332 b_skip = true;
11333 } else
11334 b_skip = true;
11335 }
11336
11337 ClipResult res = cohen_sutherland_line_clip_i(
11338 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11339 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11340
11341 plylat = plylat1;
11342 plylon = plylon1;
11343 pixx = pixxs1;
11344 pixy = pixys1;
11345 }
11346
11347 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11348 GetCanvasPointPix(plylat1, plylon1, &r1);
11349 pixx1 = r1.x;
11350 pixy1 = r1.y;
11351
11352 ClipResult res = cohen_sutherland_line_clip_i(
11353 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11354 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11355 }
11356 }
11357}
11358
11359static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
11360 const wxString &second) {
11361 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11362
11363 int pointsize = dFont->GetPointSize();
11364 pointsize /= OCPN_GetWinDIPScaleFactor();
11365
11366 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11367 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11368 false, dFont->GetFaceName());
11369
11370 dc.SetFont(*psRLI_font);
11371
11372 int w1, h1;
11373 int w2 = 0;
11374 int h2 = 0;
11375 int h, w;
11376
11377 int xp, yp;
11378 int hilite_offset = 3;
11379#ifdef __WXMAC__
11380 wxScreenDC sdc;
11381 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
11382 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
11383#else
11384 dc.GetTextExtent(first, &w1, &h1);
11385 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
11386#endif
11387
11388 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11389 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11390
11391 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
11392 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11393
11394 h = h1 + h2;
11395
11396 xp = ref_point.x - w;
11397 yp = ref_point.y;
11398 yp += hilite_offset;
11399
11400 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11401
11402 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11403 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11404
11405 dc.DrawText(first, xp, yp);
11406 if (second.Len()) dc.DrawText(second, xp, yp + h1);
11407}
11408
11409void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11410 if (!g_bAllowShipToActive) return;
11411
11412 Route *rt = g_pRouteMan->GetpActiveRoute();
11413 if (!rt) return;
11414
11415 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11416 wxPoint2DDouble pa, pb;
11418 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11419
11420 // set pen
11421 int width =
11422 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11423 if (rt->m_width != wxPENSTYLE_INVALID)
11424 width = rt->m_width; // set route pen style if any
11425 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11426 g_shipToActiveStyle, 5)]; // get setting pen style
11427 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11428 wxColour color =
11429 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11430 : // set setting route pen color
11431 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11432 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11433
11434 dc.SetPen(*mypen);
11435 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11436
11437 if (!Use_Opengl)
11438 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11439 (int)pb.m_y, GetVP(), true);
11440
11441#ifdef ocpnUSE_GL
11442 else {
11443#ifdef USE_ANDROID_GLES2
11444 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11445#else
11446 if (style != wxPENSTYLE_SOLID) {
11447 if (glChartCanvas::dash_map.find(style) !=
11448 glChartCanvas::dash_map.end()) {
11449 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11450 dc.SetPen(*mypen);
11451 }
11452 }
11453 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11454#endif
11455
11456 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11457 (int)pb.m_x, (int)pb.m_y, GetVP());
11458 }
11459#endif
11460 }
11461}
11462
11463void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11464 Route *route = 0;
11465 if (m_routeState >= 2) route = m_pMouseRoute;
11466 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11467 route = m_pMeasureRoute;
11468
11469 if (!route) return;
11470
11471 // Validate route pointer
11472 if (!g_pRouteMan->IsRouteValid(route)) return;
11473
11474 double render_lat = m_cursor_lat;
11475 double render_lon = m_cursor_lon;
11476
11477 int np = route->GetnPoints();
11478 if (np) {
11479 if (g_btouch && (np > 1)) np--;
11480 RoutePoint rp = route->GetPoint(np);
11481 render_lat = rp.m_lat;
11482 render_lon = rp.m_lon;
11483 }
11484
11485 double rhumbBearing, rhumbDist;
11486 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11487 &rhumbBearing, &rhumbDist);
11488 double brg = rhumbBearing;
11489 double dist = rhumbDist;
11490
11491 // Skip GreatCircle rubberbanding on touch devices.
11492 if (!g_btouch) {
11493 double gcBearing, gcBearing2, gcDist;
11494 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11495 m_cursor_lat, &gcDist, &gcBearing,
11496 &gcBearing2);
11497 double gcDistm = gcDist / 1852.0;
11498
11499 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11500 rhumbBearing = 90.;
11501
11502 wxPoint destPoint, lastPoint;
11503
11504 route->m_NextLegGreatCircle = false;
11505 int milesDiff = rhumbDist - gcDistm;
11506 if (milesDiff > 1) {
11507 brg = gcBearing;
11508 dist = gcDistm;
11509 route->m_NextLegGreatCircle = true;
11510 }
11511
11512 // FIXME (MacOS, the first segment is rendered wrong)
11513 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11514 &lastPoint);
11515
11516 if (route->m_NextLegGreatCircle) {
11517 for (int i = 1; i <= milesDiff; i++) {
11518 double p = (double)i * (1.0 / (double)milesDiff);
11519 double pLat, pLon;
11520 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11521 &pLon, &pLat, &gcBearing2);
11522 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11523 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11524 false);
11525 lastPoint = destPoint;
11526 }
11527 } else {
11528 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11529 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11530 false);
11531 if (m_bMeasure_DistCircle) {
11532 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11533 powf((float)(r_rband.y - lastPoint.y), 2));
11534
11535 dc.SetPen(*g_pRouteMan->GetRoutePen());
11536 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11537 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11538 }
11539 }
11540 }
11541 }
11542
11543 wxString routeInfo;
11544 double varBrg = 0;
11545 if (g_bShowTrue)
11546 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11547 0x00B0);
11548
11549 if (g_bShowMag) {
11550 double latAverage = (m_cursor_lat + render_lat) / 2;
11551 double lonAverage = (m_cursor_lon + render_lon) / 2;
11552 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11553
11554 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11555 (int)varBrg, 0x00B0);
11556 }
11557 routeInfo << " " << FormatDistanceAdaptive(dist);
11558
11559 // To make it easier to use a route as a bearing on a charted object add for
11560 // the first leg also the reverse bearing.
11561 if (np == 1) {
11562 routeInfo << "\nReverse: ";
11563 if (g_bShowTrue)
11564 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11565 (int)(brg + 180.) % 360, 0x00B0);
11566 if (g_bShowMag)
11567 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11568 (int)(varBrg + 180.) % 360, 0x00B0);
11569 }
11570
11571 wxString s0;
11572 if (!route->m_bIsInLayer)
11573 s0.Append(_("Route") + ": ");
11574 else
11575 s0.Append(_("Layer Route: "));
11576
11577 double disp_length = route->m_route_length;
11578 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11579 s0 += FormatDistanceAdaptive(disp_length);
11580
11581 RouteLegInfo(dc, r_rband, routeInfo, s0);
11582
11583 m_brepaint_piano = true;
11584}
11585
11586void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11587 if (!m_bShowVisibleSectors) return;
11588
11589 if (g_bDeferredInitDone) {
11590 // need to re-evaluate sectors?
11591 double rhumbBearing, rhumbDist;
11592 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11593 &rhumbBearing, &rhumbDist);
11594
11595 if (rhumbDist > 0.05) // miles
11596 {
11597 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11598 m_sectorlegsVisible);
11599 m_sector_glat = gLat;
11600 m_sector_glon = gLon;
11601 }
11602 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11603 }
11604}
11605
11606void ChartCanvas::WarpPointerDeferred(int x, int y) {
11607 warp_x = x;
11608 warp_y = y;
11609 warp_flag = true;
11610}
11611
11612int s_msg;
11613
11614void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11615 if (!ps52plib) return;
11616
11617 if (VPoint.b_quilt) { // quilted
11618 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11619
11620 if (m_pQuilt->IsQuiltVector()) {
11621 if (ps52plib->GetStateHash() != m_s52StateHash) {
11622 UpdateS52State();
11623 m_s52StateHash = ps52plib->GetStateHash();
11624 }
11625 }
11626 } else {
11627 if (ps52plib->GetStateHash() != m_s52StateHash) {
11628 UpdateS52State();
11629 m_s52StateHash = ps52plib->GetStateHash();
11630 }
11631 }
11632
11633 // Plugin charts
11634 bool bSendPlibState = true;
11635 if (VPoint.b_quilt) { // quilted
11636 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11637 }
11638
11639 if (bSendPlibState) {
11640 wxJSONValue v;
11641 v["OpenCPN Version Major"] = VERSION_MAJOR;
11642 v["OpenCPN Version Minor"] = VERSION_MINOR;
11643 v["OpenCPN Version Patch"] = VERSION_PATCH;
11644 v["OpenCPN Version Date"] = VERSION_DATE;
11645 v["OpenCPN Version Full"] = VERSION_FULL;
11646
11647 // S52PLIB state
11648 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11649 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11650 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11651 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11652 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11653 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11654 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11655
11656 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11657
11658 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11659 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11660
11661 // Global S52 options
11662
11663 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11664 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11665 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11666 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11667 ps52plib->m_bShowS57ImportantTextOnly;
11668 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11669 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11670 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11671 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11672 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11673
11674 // Some global GUI parameters, for completeness
11675 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11676 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11677 v["OpenCPN Scale Factor Exp"] =
11678 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11679 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11680
11681 wxJSONWriter w;
11682 wxString out;
11683 w.Write(v, out);
11684
11685 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11686 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11687 g_lastS52PLIBPluginMessage = out;
11688 }
11689 }
11690}
11691int spaint;
11692int s_in_update;
11693void ChartCanvas::OnPaint(wxPaintEvent &event) {
11694 wxPaintDC dc(this);
11695
11696 // GetToolbar()->Show( m_bToolbarEnable );
11697
11698 // Paint updates may have been externally disabled (temporarily, to avoid
11699 // Yield() recursion performance loss) It is important that the wxPaintDC is
11700 // built, even if we elect to not process this paint message. Otherwise, the
11701 // paint message may not be removed from the message queue, esp on Windows.
11702 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11703
11704 if (!m_b_paint_enable) {
11705 return;
11706 }
11707
11708 // If necessary, reconfigure the S52 PLIB
11709 UpdateCanvasS52PLIBConfig();
11710
11711#ifdef ocpnUSE_GL
11712 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11713
11714 if (m_glcc && g_bopengl) {
11715 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11716 s_in_update++;
11717 m_glcc->Update();
11718 s_in_update--;
11719 }
11720
11721 return;
11722 }
11723#endif
11724
11725 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11726
11727 wxRegion ru = GetUpdateRegion();
11728
11729 int rx, ry, rwidth, rheight;
11730 ru.GetBox(rx, ry, rwidth, rheight);
11731
11732#ifdef ocpnUSE_DIBSECTION
11733 ocpnMemDC temp_dc;
11734#else
11735 wxMemoryDC temp_dc;
11736#endif
11737
11738 long height = GetVP().pix_height;
11739
11740#ifdef __WXMAC__
11741 // On OS X we have to explicitly extend the region for the piano area
11742 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11743 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11744 height += m_Piano->GetHeight();
11745#endif // __WXMAC__
11746 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11747
11748 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11749 if (pthumbwin) {
11750 int thumbx, thumby, thumbsx, thumbsy;
11751 pthumbwin->GetPosition(&thumbx, &thumby);
11752 pthumbwin->GetSize(&thumbsx, &thumbsy);
11753 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11754
11755 if (pthumbwin->IsShown()) {
11756 rgn_chart.Subtract(rgn_thumbwin);
11757 ru.Subtract(rgn_thumbwin);
11758 }
11759 }
11760
11761 // subtract the chart bar if it isn't transparent, and determine if we need to
11762 // paint it
11763 wxRegion rgn_blit = ru;
11764 if (g_bShowChartBar) {
11765 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11766 GetClientSize().x, m_Piano->GetHeight());
11767
11768 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11769 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11770 if (style->chartStatusWindowTransparent)
11771 m_brepaint_piano = true;
11772 else
11773 ru.Subtract(chart_bar_rect);
11774 }
11775 }
11776
11777 if (m_Compass && m_Compass->IsShown()) {
11778 wxRect compassRect = m_Compass->GetRect();
11779 if (ru.Contains(compassRect) != wxOutRegion) {
11780 ru.Subtract(compassRect);
11781 }
11782 }
11783
11784 wxRect noteRect = m_notification_button->GetRect();
11785 if (ru.Contains(noteRect) != wxOutRegion) {
11786 ru.Subtract(noteRect);
11787 }
11788
11789 // Is this viewpoint the same as the previously painted one?
11790 bool b_newview = true;
11791
11792 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11793 (m_cache_vp.rotation == VPoint.rotation) &&
11794 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11795 m_cache_vp.IsValid()) {
11796 b_newview = false;
11797 }
11798
11799 // If the ViewPort is skewed or rotated, we may be able to use the cached
11800 // rotated bitmap.
11801 bool b_rcache_ok = false;
11802 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11803 b_rcache_ok = !b_newview;
11804
11805 // Make a special VP
11806 if (VPoint.b_MercatorProjectionOverride)
11807 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11808 ViewPort svp = VPoint;
11809
11810 svp.pix_width = svp.rv_rect.width;
11811 svp.pix_height = svp.rv_rect.height;
11812
11813 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11814 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11815 // VPoint.rv_rect.height);
11816
11817 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11818
11819 // If we are going to use the cached rotated image, there is no need to fetch
11820 // any chart data and this will do it...
11821 if (b_rcache_ok) chart_get_region.Clear();
11822
11823 // Blit pan acceleration
11824 if (VPoint.b_quilt) // quilted
11825 {
11826 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11827
11828 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11829
11830 bool busy = false;
11831 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11832 m_cache_vp.rotation != VPoint.rotation)) {
11833 AbstractPlatform::ShowBusySpinner();
11834 busy = true;
11835 }
11836
11837 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11838 (m_working_bm.GetHeight() != svp.pix_height))
11839 m_working_bm.Create(svp.pix_width, svp.pix_height,
11840 -1); // make sure the target is big enoug
11841
11842 if (fabs(VPoint.rotation) < 0.01) {
11843 bool b_save = true;
11844
11845 if (g_SencThreadManager) {
11846 if (g_SencThreadManager->GetJobCount()) {
11847 b_save = false;
11848 m_cache_vp.Invalidate();
11849 }
11850 }
11851
11852 // If the saved wxBitmap from last OnPaint is useable
11853 // calculate the blit parameters
11854
11855 // We can only do screen blit painting if subsequent ViewPorts differ by
11856 // whole pixels So, in small scale bFollow mode, force the full screen
11857 // render. This seems a hack....There may be better logic here.....
11858
11859 // if(m_bFollow)
11860 // b_save = false;
11861
11862 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11863 if (b_newview) {
11864 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11865 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11866
11867 int dy = c_new.y - c_old.y;
11868 int dx = c_new.x - c_old.x;
11869
11870 // printf("In OnPaint Trying Blit dx: %d
11871 // dy:%d\n\n", dx, dy);
11872
11873 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11874 if (dx || dy) {
11875 // Blit the reuseable portion of the cached wxBitmap to a working
11876 // bitmap
11877 temp_dc.SelectObject(m_working_bm);
11878
11879 wxMemoryDC cache_dc;
11880 cache_dc.SelectObject(m_cached_chart_bm);
11881
11882 if (dy > 0) {
11883 if (dx > 0) {
11884 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11885 VPoint.pix_height - dy, &cache_dc, dx, dy);
11886 } else {
11887 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11888 VPoint.pix_height - dy, &cache_dc, 0, dy);
11889 }
11890
11891 } else {
11892 if (dx > 0) {
11893 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11894 VPoint.pix_height + dy, &cache_dc, dx, 0);
11895 } else {
11896 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11897 VPoint.pix_height + dy, &cache_dc, 0, 0);
11898 }
11899 }
11900
11901 OCPNRegion update_region;
11902 if (dy) {
11903 if (dy > 0)
11904 update_region.Union(
11905 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11906 else
11907 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11908 }
11909
11910 if (dx) {
11911 if (dx > 0)
11912 update_region.Union(
11913 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11914 else
11915 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11916 }
11917
11918 // Render the new region
11919 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11920 update_region);
11921 cache_dc.SelectObject(wxNullBitmap);
11922 } else {
11923 // No sensible (dx, dy) change in the view, so use the cached
11924 // member bitmap
11925 temp_dc.SelectObject(m_cached_chart_bm);
11926 b_save = false;
11927 }
11928 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11929
11930 } else // not blitable
11931 {
11932 temp_dc.SelectObject(m_working_bm);
11933 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11934 chart_get_region);
11935 }
11936 } else {
11937 // No change in the view, so use the cached member bitmap2
11938 temp_dc.SelectObject(m_cached_chart_bm);
11939 b_save = false;
11940 }
11941 } else // cached bitmap is not yet valid
11942 {
11943 temp_dc.SelectObject(m_working_bm);
11944 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11945 chart_get_region);
11946 }
11947
11948 // Save the fully rendered quilt image as a wxBitmap member of this class
11949 if (b_save) {
11950 // if((m_cached_chart_bm.GetWidth() !=
11951 // svp.pix_width) ||
11952 // (m_cached_chart_bm.GetHeight() !=
11953 // svp.pix_height))
11954 // m_cached_chart_bm.Create(svp.pix_width,
11955 // svp.pix_height, -1); // target wxBitmap
11956 // is big enough
11957 wxMemoryDC scratch_dc_0;
11958 scratch_dc_0.SelectObject(m_cached_chart_bm);
11959 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11960
11961 scratch_dc_0.SelectObject(wxNullBitmap);
11962
11963 m_bm_cache_vp =
11964 VPoint; // save the ViewPort associated with the cached wxBitmap
11965 }
11966 }
11967
11968 else // quilted, rotated
11969 {
11970 temp_dc.SelectObject(m_working_bm);
11971 OCPNRegion chart_get_all_region(
11972 wxRect(0, 0, svp.pix_width, svp.pix_height));
11973 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11974 chart_get_all_region);
11975 }
11976
11977 AbstractPlatform::HideBusySpinner();
11978
11979 }
11980
11981 else // not quilted
11982 {
11983 if (!m_singleChart) {
11984 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
11985 dc.Clear();
11986 return;
11987 }
11988
11989 if (!chart_get_region.IsEmpty()) {
11990 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
11991 }
11992 }
11993
11994 if (temp_dc.IsOk()) {
11995 // Arrange to render the World Chart vector data behind the rendered
11996 // current chart so that uncovered canvas areas show at least the world
11997 // chart.
11998 OCPNRegion chartValidRegion;
11999 if (!VPoint.b_quilt) {
12000 // Make a region covering the current chart on the canvas
12001
12002 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12003 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12004 else {
12005 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12006 // require that the viewport passed here have pix_width and pix_height
12007 // set to the actual display, not the virtual (rv_rect) sizes
12008 // (the vector calculations require the virtual sizes in svp)
12009
12010 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12011 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12012 }
12013 } else
12014 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12015
12016 temp_dc.DestroyClippingRegion();
12017
12018 // Copy current chart region
12019 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12020
12021 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12022
12023 if (!backgroundRegion.IsEmpty()) {
12024 // Draw the Background Chart only in the areas NOT covered by the
12025 // current chart view
12026
12027 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12028 clipping regions with more than 1 rectangle so... */
12029 wxColour water = pWorldBackgroundChart->water;
12030 if (water.IsOk()) {
12031 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12032 temp_dc.SetBrush(wxBrush(water));
12033 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12034 while (upd.HaveRects()) {
12035 wxRect rect = upd.GetRect();
12036 temp_dc.DrawRectangle(rect);
12037 upd.NextRect();
12038 }
12039 }
12040 // Associate with temp_dc
12041 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12042 temp_dc.SetDeviceClippingRegion(*clip_region);
12043 delete clip_region;
12044
12045 ocpnDC bgdc(temp_dc);
12046 double r = VPoint.rotation;
12047 SetVPRotation(VPoint.skew);
12048
12049 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12050 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12051
12052 SetVPRotation(r);
12053 }
12054 } // temp_dc.IsOk();
12055
12056 wxMemoryDC *pChartDC = &temp_dc;
12057 wxMemoryDC rotd_dc;
12058
12059 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12060 // Can we use the current rotated image cache?
12061 if (!b_rcache_ok) {
12062#ifdef __WXMSW__
12063 wxMemoryDC tbase_dc;
12064 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12065 tbase_dc.SelectObject(bm_base);
12066 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12067 tbase_dc.SelectObject(wxNullBitmap);
12068#else
12069 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12070#endif
12071
12072 wxImage base_image;
12073 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12074
12075 // Use a local static image rotator to improve wxWidgets code profile
12076 // Especially, on GTK the wxRound and wxRealPoint functions are very
12077 // expensive.....
12078
12079 double angle = GetVP().skew - GetVP().rotation;
12080 wxImage ri;
12081 bool b_rot_ok = false;
12082 if (base_image.IsOk()) {
12083 ViewPort rot_vp = GetVP();
12084
12085 m_b_rot_hidef = false;
12086
12087 ri = Image_Rotate(
12088 base_image, angle,
12089 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12090 m_b_rot_hidef, &m_roffset);
12091
12092 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12093 (rot_vp.rotation == VPoint.rotation) &&
12094 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12095 rot_vp.IsValid() && (ri.IsOk())) {
12096 b_rot_ok = true;
12097 }
12098 }
12099
12100 if (b_rot_ok) {
12101 delete m_prot_bm;
12102 m_prot_bm = new wxBitmap(ri);
12103 }
12104
12105 m_roffset.x += VPoint.rv_rect.x;
12106 m_roffset.y += VPoint.rv_rect.y;
12107 }
12108
12109 if (m_prot_bm && m_prot_bm->IsOk()) {
12110 rotd_dc.SelectObject(*m_prot_bm);
12111 pChartDC = &rotd_dc;
12112 } else {
12113 pChartDC = &temp_dc;
12114 m_roffset = wxPoint(0, 0);
12115 }
12116 } else { // unrotated
12117 pChartDC = &temp_dc;
12118 m_roffset = wxPoint(0, 0);
12119 }
12120
12121 wxPoint offset = m_roffset;
12122
12123 // Save the PixelCache viewpoint for next time
12124 m_cache_vp = VPoint;
12125
12126 // Set up a scratch DC for overlay objects
12127 wxMemoryDC mscratch_dc;
12128 mscratch_dc.SelectObject(*pscratch_bm);
12129
12130 mscratch_dc.ResetBoundingBox();
12131 mscratch_dc.DestroyClippingRegion();
12132 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12133
12134 // Blit the externally invalidated areas of the chart onto the scratch dc
12135 wxRegionIterator upd(rgn_blit); // get the update rect list
12136 while (upd) {
12137 wxRect rect = upd.GetRect();
12138
12139 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12140 rect.x - offset.x, rect.y - offset.y);
12141 upd++;
12142 }
12143
12144 // If multi-canvas, indicate which canvas has keyboard focus
12145 // by drawing a simple blue bar at the top.
12146 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12147 if (this == wxWindow::FindFocus()) {
12148 g_focusCanvas = this;
12149
12150 wxColour colour = GetGlobalColor("BLUE4");
12151 mscratch_dc.SetPen(wxPen(colour));
12152 mscratch_dc.SetBrush(wxBrush(colour));
12153
12154 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12155 mscratch_dc.DrawRectangle(activeRect);
12156 }
12157 }
12158
12159 // Any MBtiles?
12160 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12161 unsigned int im = stackIndexArray.size();
12162 if (VPoint.b_quilt && im > 0) {
12163 std::vector<int> tiles_to_show;
12164 for (unsigned int is = 0; is < im; is++) {
12165 const ChartTableEntry &cte =
12166 ChartData->GetChartTableEntry(stackIndexArray[is]);
12167 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12168 continue;
12169 }
12170 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12171 tiles_to_show.push_back(stackIndexArray[is]);
12172 }
12173 }
12174
12175 if (tiles_to_show.size())
12176 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12177 }
12178
12179 // May get an unexpected OnPaint call while switching display modes
12180 // Guard for that.
12181 if (!g_bopengl) {
12182 ocpnDC scratch_dc(mscratch_dc);
12183 RenderAlertMessage(mscratch_dc, GetVP());
12184 }
12185
12186#if 0
12187 // quiting?
12188 if (g_bquiting) {
12189#ifdef ocpnUSE_DIBSECTION
12190 ocpnMemDC q_dc;
12191#else
12192 wxMemoryDC q_dc;
12193#endif
12194 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12195 q_dc.SelectObject(qbm);
12196
12197 // Get a copy of the screen
12198 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12199
12200 // Draw a rectangle over the screen with a stipple brush
12201 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12202 q_dc.SetBrush(qbr);
12203 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12204
12205 // Blit back into source
12206 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12207 wxCOPY);
12208
12209 q_dc.SelectObject(wxNullBitmap);
12210 }
12211#endif
12212
12213#if 0
12214 // It is possible that this two-step method may be reuired for some platforms.
12215 // So, retain in the code base to aid recovery if necessary
12216
12217 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12218 if( VPoint.b_quilt ) {
12219 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12220 ChartBase *chart = m_pQuilt->GetRefChart();
12221 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12222
12223 // Clear the text Global declutter list
12224 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12225 if(ChPI)
12226 ChPI->ClearPLIBTextList();
12227 else{
12228 if(ps52plib)
12229 ps52plib->ClearTextList();
12230 }
12231
12232 wxMemoryDC t_dc;
12233 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12234
12235 wxColor maskBackground = wxColour(1,0,0);
12236 t_dc.SelectObject( qbm );
12237 t_dc.SetBackground(wxBrush(maskBackground));
12238 t_dc.Clear();
12239
12240 // Copy the scratch DC into the new bitmap
12241 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12242
12243 // Render the text to the new bitmap
12244 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12245 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12246
12247 // Copy the new bitmap back to the scratch dc
12248 wxRegionIterator upd_final( ru );
12249 while( upd_final ) {
12250 wxRect rect = upd_final.GetRect();
12251 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12252 upd_final++;
12253 }
12254
12255 t_dc.SelectObject( wxNullBitmap );
12256 }
12257 }
12258 }
12259#endif
12260 // Direct rendering model...
12261 if (VPoint.b_quilt) {
12262 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12263 ChartBase *chart = m_pQuilt->GetRefChart();
12264 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12265 // Clear the text Global declutter list
12266 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12267 if (ChPI)
12268 ChPI->ClearPLIBTextList();
12269 else {
12270 if (ps52plib) ps52plib->ClearTextList();
12271 }
12272
12273 // Render the text directly to the scratch bitmap
12274 OCPNRegion chart_all_text_region(
12275 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12276
12277 if (g_bShowChartBar && m_Piano) {
12278 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12279 GetVP().pix_width, m_Piano->GetHeight());
12280
12281 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12282 if (!style->chartStatusWindowTransparent)
12283 chart_all_text_region.Subtract(chart_bar_rect);
12284 }
12285
12286 if (m_Compass && m_Compass->IsShown()) {
12287 wxRect compassRect = m_Compass->GetRect();
12288 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12289 chart_all_text_region.Subtract(compassRect);
12290 }
12291 }
12292
12293 mscratch_dc.DestroyClippingRegion();
12294
12295 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12296 chart_all_text_region);
12297 }
12298 }
12299 }
12300
12301 // Now that charts are fully rendered, apply the overlay objects as decals.
12302 ocpnDC scratch_dc(mscratch_dc);
12303 DrawOverlayObjects(scratch_dc, ru);
12304
12305 // And finally, blit the scratch dc onto the physical dc
12306 wxRegionIterator upd_final(rgn_blit);
12307 while (upd_final) {
12308 wxRect rect = upd_final.GetRect();
12309 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12310 rect.y);
12311 upd_final++;
12312 }
12313
12314 // Deselect the chart bitmap from the temp_dc, so that it will not be
12315 // destroyed in the temp_dc dtor
12316 temp_dc.SelectObject(wxNullBitmap);
12317 // And for the scratch bitmap
12318 mscratch_dc.SelectObject(wxNullBitmap);
12319
12320 dc.DestroyClippingRegion();
12321
12322 PaintCleanup();
12323}
12324
12325void ChartCanvas::PaintCleanup() {
12326 // Handle the current graphic window, if present
12327 if (m_inPinch) return;
12328
12329 if (pCwin) {
12330 pCwin->Show();
12331 if (m_bTCupdate) {
12332 pCwin->Refresh();
12333 pCwin->Update();
12334 }
12335 }
12336
12337 // And set flags for next time
12338 m_bTCupdate = false;
12339
12340 // Handle deferred WarpPointer
12341 if (warp_flag) {
12342 WarpPointer(warp_x, warp_y);
12343 warp_flag = false;
12344 }
12345
12346 // Start movement timers, this runs nearly immediately.
12347 // the reason we cannot simply call it directly is the
12348 // refresh events it emits may be blocked from this paint event
12349 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12350 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12351}
12352
12353#if 0
12354wxColour GetErrorGraphicColor(double val)
12355{
12356 /*
12357 double valm = wxMin(val_max, val);
12358
12359 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12360 unsigned char red = (unsigned char)(255 * (valm/val_max));
12361
12362 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12363
12364 hv.saturation = 1.0;
12365 hv.value = 1.0;
12366
12367 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12368 return wxColour(rv.red, rv.green, rv.blue);
12369 */
12370
12371 // HTML colors taken from NOAA WW3 Web representation
12372 wxColour c;
12373 if((val > 0) && (val < 1)) c.Set("#002ad9");
12374 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12375 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12376 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12377 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12378 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12379 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12380 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12381 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12382 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12383 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12384 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12385 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12386 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12387 else if((val >= 30) && (val < 36)) c.Set("#870000");
12388 else if((val >= 36) && (val < 42)) c.Set("#690000");
12389 else if((val >= 42) && (val < 48)) c.Set("#550000");
12390 else if( val >= 48) c.Set("#410000");
12391
12392 return c;
12393}
12394
12395void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12396{
12397 wxImage gr_image(vp->pix_width, vp->pix_height);
12398 gr_image.InitAlpha();
12399
12400 double maxval = -10000;
12401 double minval = 10000;
12402
12403 double rlat, rlon;
12404 double glat, glon;
12405
12406 GetCanvasPixPoint(0, 0, rlat, rlon);
12407
12408 for(int i=1; i < vp->pix_height-1; i++)
12409 {
12410 for(int j=0; j < vp->pix_width; j++)
12411 {
12412 // Reference mercator value
12413// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12414
12415 // Georef value
12416 GetCanvasPixPoint(j, i, glat, glon);
12417
12418 maxval = wxMax(maxval, (glat - rlat));
12419 minval = wxMin(minval, (glat - rlat));
12420
12421 }
12422 rlat = glat;
12423 }
12424
12425 GetCanvasPixPoint(0, 0, rlat, rlon);
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 double f = ((glat - rlat)-minval)/(maxval - minval);
12437
12438 double dy = (f * 40);
12439
12440 wxColour c = GetErrorGraphicColor(dy);
12441 unsigned char r = c.Red();
12442 unsigned char g = c.Green();
12443 unsigned char b = c.Blue();
12444
12445 gr_image.SetRGB(j, i, r,g,b);
12446 if((glat - rlat )!= 0)
12447 gr_image.SetAlpha(j, i, 128);
12448 else
12449 gr_image.SetAlpha(j, i, 255);
12450
12451 }
12452 rlat = glat;
12453 }
12454
12455 // Create a Bitmap
12456 wxBitmap *pbm = new wxBitmap(gr_image);
12457 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12458 pbm->SetMask(gr_mask);
12459
12460 pmdc->DrawBitmap(*pbm, 0,0);
12461
12462 delete pbm;
12463
12464}
12465
12466#endif
12467
12468void ChartCanvas::CancelMouseRoute() {
12469 m_routeState = 0;
12470 m_pMouseRoute = NULL;
12471 m_bDrawingRoute = false;
12472}
12473
12474int ChartCanvas::GetNextContextMenuId() {
12475 return CanvasMenuHandler::GetNextContextMenuId();
12476}
12477
12478bool ChartCanvas::SetCursor(const wxCursor &c) {
12479#ifdef ocpnUSE_GL
12480 if (g_bopengl && m_glcc)
12481 return m_glcc->SetCursor(c);
12482 else
12483#endif
12484 return wxWindow::SetCursor(c);
12485}
12486
12487void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12488 if (g_bquiting) return;
12489 // Keep the mouse position members up to date
12490 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12491
12492 // Retrigger the route leg popup timer
12493 // This handles the case when the chart is moving in auto-follow mode,
12494 // but no user mouse input is made. The timer handler may Hide() the
12495 // popup if the chart moved enough n.b. We use slightly longer oneshot
12496 // value to allow this method's Refresh() to complete before potentially
12497 // getting another Refresh() in the popup timer handler.
12498 if (!m_RolloverPopupTimer.IsRunning() &&
12499 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12500 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12501 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12502 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12503
12504#ifdef ocpnUSE_GL
12505 if (m_glcc && g_bopengl) {
12506 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12507 // overlay objects.
12508 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12509
12510 m_glcc->Refresh(eraseBackground,
12511 NULL); // We always are going to render the entire screen
12512 // anyway, so make
12513 // sure that the window managers understand the invalid area
12514 // is actually the entire client area.
12515
12516 // We need to selectively Refresh some child windows, if they are visible.
12517 // Note that some children are refreshed elsewhere on timer ticks, so don't
12518 // need attention here.
12519
12520 // Thumbnail chart
12521 if (pthumbwin && pthumbwin->IsShown()) {
12522 pthumbwin->Raise();
12523 pthumbwin->Refresh(false);
12524 }
12525
12526 // ChartInfo window
12527 if (m_pCIWin && m_pCIWin->IsShown()) {
12528 m_pCIWin->Raise();
12529 m_pCIWin->Refresh(false);
12530 }
12531
12532 // if(g_MainToolbar)
12533 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12534
12535 } else
12536#endif
12537 wxWindow::Refresh(eraseBackground, rect);
12538}
12539
12540void ChartCanvas::Update() {
12541 if (m_glcc && g_bopengl) {
12542#ifdef ocpnUSE_GL
12543 m_glcc->Update();
12544#endif
12545 } else
12546 wxWindow::Update();
12547}
12548
12549void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12550 if (!pemboss) return;
12551 int x = pemboss->x, y = pemboss->y;
12552 const double factor = 200;
12553
12554 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12555 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12556 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12557
12558 // Grab a snipped image out of the chart
12559 wxMemoryDC snip_dc;
12560 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12561 snip_dc.SelectObject(snip_bmp);
12562
12563 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12564 snip_dc.SelectObject(wxNullBitmap);
12565
12566 wxImage snip_img = snip_bmp.ConvertToImage();
12567
12568 // Apply Emboss map to the snip image
12569 unsigned char *pdata = snip_img.GetData();
12570 if (pdata) {
12571 for (int y = 0; y < pemboss->height; y++) {
12572 int map_index = (y * pemboss->width);
12573 for (int x = 0; x < pemboss->width; x++) {
12574 double val = (pemboss->pmap[map_index] * factor) / 256.;
12575
12576 int nred = (int)((*pdata) + val);
12577 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12578 *pdata++ = (unsigned char)nred;
12579
12580 int ngreen = (int)((*pdata) + val);
12581 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12582 *pdata++ = (unsigned char)ngreen;
12583
12584 int nblue = (int)((*pdata) + val);
12585 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12586 *pdata++ = (unsigned char)nblue;
12587
12588 map_index++;
12589 }
12590 }
12591 }
12592
12593 // Convert embossed snip to a bitmap
12594 wxBitmap emb_bmp(snip_img);
12595
12596 // Map to another memoryDC
12597 wxMemoryDC result_dc;
12598 result_dc.SelectObject(emb_bmp);
12599
12600 // Blit to target
12601 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12602
12603 result_dc.SelectObject(wxNullBitmap);
12604}
12605
12606emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12607 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12608
12609 if (GetQuiltMode()) {
12610 // disable Overzoom indicator for MBTiles
12611 int refIndex = GetQuiltRefChartdbIndex();
12612 if (refIndex >= 0) {
12613 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12614 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12615 if (current_type == CHART_TYPE_MBTILES) {
12616 ChartBase *pChart = m_pQuilt->GetRefChart();
12617 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12618 if (ptc) {
12619 zoom_factor = ptc->GetZoomFactor();
12620 }
12621 }
12622 }
12623
12624 if (zoom_factor <= 3.9) return NULL;
12625 } else {
12626 if (m_singleChart) {
12627 if (zoom_factor <= 3.9) return NULL;
12628 } else
12629 return NULL;
12630 }
12631
12632 if (m_pEM_OverZoom) {
12633 m_pEM_OverZoom->x = 4;
12634 m_pEM_OverZoom->y = 0;
12635 if (g_MainToolbar && IsPrimaryCanvas()) {
12636 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12637 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12638 }
12639 }
12640 return m_pEM_OverZoom;
12641}
12642
12643void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12644 GridDraw(dc);
12645
12646 // bool pluginOverlayRender = true;
12647 //
12648 // if(g_canvasConfig > 0){ // Multi canvas
12649 // if(IsPrimaryCanvas())
12650 // pluginOverlayRender = false;
12651 // }
12652
12653 g_overlayCanvas = this;
12654
12655 if (g_pi_manager) {
12656 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12657 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12659 }
12660
12661 AISDrawAreaNotices(dc, GetVP(), this);
12662
12663 wxDC *pdc = dc.GetDC();
12664 if (pdc) {
12665 pdc->DestroyClippingRegion();
12666 wxDCClipper(*pdc, ru);
12667 }
12668
12669 if (m_bShowNavobjects) {
12670 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12671 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12672 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12673 DrawAnchorWatchPoints(dc);
12674 } else {
12675 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12676 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12677 }
12678
12679 AISDraw(dc, GetVP(), this);
12680 ShipDraw(dc);
12681 AlertDraw(dc);
12682
12683 RenderVisibleSectorLights(dc);
12684
12685 RenderAllChartOutlines(dc, GetVP());
12686 RenderRouteLegs(dc);
12687 RenderShipToActive(dc, false);
12688 ScaleBarDraw(dc);
12689 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12690 if (g_pi_manager) {
12691 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12693 }
12694
12695 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12696 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12697
12698 if (g_pi_manager) {
12699 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12701 }
12702
12703 if (m_bShowTide) {
12704 RebuildTideSelectList(GetVP().GetBBox());
12705 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12706 }
12707
12708 if (m_bShowCurrent) {
12709 RebuildCurrentSelectList(GetVP().GetBBox());
12710 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12711 }
12712
12713 if (!g_PrintingInProgress) {
12714 if (IsPrimaryCanvas()) {
12715 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12716 }
12717
12718 if (IsPrimaryCanvas()) {
12719 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12720 }
12721
12722 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12723
12724 if (m_pTrackRolloverWin) {
12725 m_pTrackRolloverWin->Draw(dc);
12726 m_brepaint_piano = true;
12727 }
12728
12729 if (m_pRouteRolloverWin) {
12730 m_pRouteRolloverWin->Draw(dc);
12731 m_brepaint_piano = true;
12732 }
12733
12734 if (m_pAISRolloverWin) {
12735 m_pAISRolloverWin->Draw(dc);
12736 m_brepaint_piano = true;
12737 }
12738 if (m_brepaint_piano && g_bShowChartBar) {
12739 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12740 }
12741
12742 if (m_Compass) m_Compass->Paint(dc);
12743
12744 if (!g_CanvasHideNotificationIcon) {
12745 auto &noteman = NotificationManager::GetInstance();
12746 if (noteman.GetNotificationCount()) {
12747 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12748 if (m_notification_button->UpdateStatus()) Refresh();
12749 m_notification_button->Show(true);
12750 m_notification_button->Paint(dc);
12751 } else {
12752 m_notification_button->Show(false);
12753 }
12754 }
12755 }
12756 if (g_pi_manager) {
12757 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12759 }
12760}
12761
12762emboss_data *ChartCanvas::EmbossDepthScale() {
12763 if (!m_bShowDepthUnits) return NULL;
12764
12765 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12766
12767 if (GetQuiltMode()) {
12768 wxString s = m_pQuilt->GetQuiltDepthUnit();
12769 s.MakeUpper();
12770 if (s == "FEET")
12771 depth_unit_type = DEPTH_UNIT_FEET;
12772 else if (s.StartsWith("FATHOMS"))
12773 depth_unit_type = DEPTH_UNIT_FATHOMS;
12774 else if (s.StartsWith("METERS"))
12775 depth_unit_type = DEPTH_UNIT_METERS;
12776 else if (s.StartsWith("METRES"))
12777 depth_unit_type = DEPTH_UNIT_METERS;
12778 else if (s.StartsWith("METRIC"))
12779 depth_unit_type = DEPTH_UNIT_METERS;
12780 else if (s.StartsWith("METER"))
12781 depth_unit_type = DEPTH_UNIT_METERS;
12782
12783 } else {
12784 if (m_singleChart) {
12785 depth_unit_type = m_singleChart->GetDepthUnitType();
12786 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12787 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12788 }
12789 }
12790
12791 emboss_data *ped = NULL;
12792 switch (depth_unit_type) {
12793 case DEPTH_UNIT_FEET:
12794 ped = m_pEM_Feet;
12795 break;
12796 case DEPTH_UNIT_METERS:
12797 ped = m_pEM_Meters;
12798 break;
12799 case DEPTH_UNIT_FATHOMS:
12800 ped = m_pEM_Fathoms;
12801 break;
12802 default:
12803 return NULL;
12804 }
12805
12806 ped->x = (GetVP().pix_width - ped->width);
12807
12808 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12809 wxRect r = m_Compass->GetRect();
12810 ped->y = r.y + r.height;
12811 } else {
12812 ped->y = 40;
12813 }
12814 return ped;
12815}
12816
12817void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12818 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12819 wxFont font;
12820 if (style->embossFont == wxEmptyString) {
12821 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12822 font = *dFont;
12823 font.SetPointSize(60);
12824 font.SetWeight(wxFONTWEIGHT_BOLD);
12825 } else
12826 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12827 wxFONTWEIGHT_BOLD, false, style->embossFont);
12828
12829 int emboss_width = 500;
12830 int emboss_height = 200;
12831
12832 // Free any existing emboss maps
12833 delete m_pEM_Feet;
12834 delete m_pEM_Meters;
12835 delete m_pEM_Fathoms;
12836
12837 // Create the 3 DepthUnit emboss map structures
12838 m_pEM_Feet =
12839 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12840 m_pEM_Meters =
12841 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12842 m_pEM_Fathoms =
12843 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12844}
12845
12846#define OVERZOOM_TEXT _("OverZoom")
12847
12848void ChartCanvas::SetOverzoomFont() {
12849 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12850 int w, h;
12851
12852 wxFont font;
12853 if (style->embossFont == wxEmptyString) {
12854 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12855 font = *dFont;
12856 font.SetPointSize(40);
12857 font.SetWeight(wxFONTWEIGHT_BOLD);
12858 } else
12859 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12860 wxFONTWEIGHT_BOLD, false, style->embossFont);
12861
12862 wxClientDC dc(this);
12863 dc.SetFont(font);
12864 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12865
12866 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12867 font.SetPointSize(font.GetPointSize() - 1);
12868 dc.SetFont(font);
12869 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12870 }
12871 m_overzoomFont = font;
12872 m_overzoomTextWidth = w;
12873 m_overzoomTextHeight = h;
12874}
12875
12876void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12877 delete m_pEM_OverZoom;
12878
12879 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12880 m_pEM_OverZoom =
12881 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12882 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12883}
12884
12885emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12886 int height, const wxString &str,
12887 ColorScheme cs) {
12888 int *pmap;
12889
12890 // Create a temporary bitmap
12891 wxBitmap bmp(width, height, -1);
12892
12893 // Create a memory DC
12894 wxMemoryDC temp_dc;
12895 temp_dc.SelectObject(bmp);
12896
12897 // Paint on it
12898 temp_dc.SetBackground(*wxWHITE_BRUSH);
12899 temp_dc.SetTextBackground(*wxWHITE);
12900 temp_dc.SetTextForeground(*wxBLACK);
12901
12902 temp_dc.Clear();
12903
12904 temp_dc.SetFont(font);
12905
12906 int str_w, str_h;
12907 temp_dc.GetTextExtent(str, &str_w, &str_h);
12908 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12909 temp_dc.DrawText(str, 1, 1);
12910
12911 // Deselect the bitmap
12912 temp_dc.SelectObject(wxNullBitmap);
12913
12914 // Convert bitmap the wxImage for manipulation
12915 wxImage img = bmp.ConvertToImage();
12916
12917 int image_width = str_w * 105 / 100;
12918 int image_height = str_h * 105 / 100;
12919 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12920 wxMin(image_height, img.GetHeight()));
12921 wxImage imgs = img.GetSubImage(r);
12922
12923 double val_factor;
12924 switch (cs) {
12925 case GLOBAL_COLOR_SCHEME_DAY:
12926 default:
12927 val_factor = 1;
12928 break;
12929 case GLOBAL_COLOR_SCHEME_DUSK:
12930 val_factor = .5;
12931 break;
12932 case GLOBAL_COLOR_SCHEME_NIGHT:
12933 val_factor = .25;
12934 break;
12935 }
12936
12937 int val;
12938 int index;
12939 const int w = imgs.GetWidth();
12940 const int h = imgs.GetHeight();
12941 pmap = (int *)calloc(w * h * sizeof(int), 1);
12942 // Create emboss map by differentiating the emboss image
12943 // and storing integer results in pmap
12944 // n.b. since the image is B/W, it is sufficient to check
12945 // one channel (i.e. red) only
12946 for (int y = 1; y < h - 1; y++) {
12947 for (int x = 1; x < w - 1; x++) {
12948 val =
12949 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
12950 val = (int)(val * val_factor);
12951 index = (y * w) + x;
12952 pmap[index] = val;
12953 }
12954 }
12955
12956 emboss_data *pret = new emboss_data;
12957 pret->pmap = pmap;
12958 pret->width = w;
12959 pret->height = h;
12960
12961 return pret;
12962}
12963
12964void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12965 Track *active_track = NULL;
12966 for (Track *pTrackDraw : g_TrackList) {
12967 if (g_pActiveTrack == pTrackDraw) {
12968 active_track = pTrackDraw;
12969 continue;
12970 }
12971
12972 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
12973 }
12974
12975 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12976}
12977
12978void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12979 Track *active_track = NULL;
12980 for (Track *pTrackDraw : g_TrackList) {
12981 if (g_pActiveTrack == pTrackDraw) {
12982 active_track = pTrackDraw;
12983 break;
12984 }
12985 }
12986 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12987}
12988
12989void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12990 Route *active_route = NULL;
12991 for (Route *pRouteDraw : *pRouteList) {
12992 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12993 active_route = pRouteDraw;
12994 continue;
12995 }
12996
12997 // if(m_canvasIndex == 1)
12998 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
12999 }
13000
13001 // Draw any active or selected route (or track) last, so that is is always on
13002 // top
13003 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13004}
13005
13006void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13007 Route *active_route = NULL;
13008
13009 for (Route *pRouteDraw : *pRouteList) {
13010 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13011 active_route = pRouteDraw;
13012 break;
13013 }
13014 }
13015 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13016}
13017
13018void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13019 if (!pWayPointMan) return;
13020
13021 auto node = pWayPointMan->GetWaypointList()->begin();
13022
13023 while (node != pWayPointMan->GetWaypointList()->end()) {
13024 RoutePoint *pWP = *node;
13025 if (pWP) {
13026 if (pWP->m_bIsInRoute) {
13027 ++node;
13028 continue;
13029 }
13030
13031 /* technically incorrect... waypoint has bounding box */
13032 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13033 RoutePointGui(*pWP).Draw(dc, this, NULL);
13034 else {
13035 // Are Range Rings enabled?
13036 if (pWP->GetShowWaypointRangeRings() &&
13037 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13038 double factor = 1.00;
13039 if (pWP->GetWaypointRangeRingsStepUnits() ==
13040 1) // convert kilometers to NMi
13041 factor = 1 / 1.852;
13042
13043 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13044 pWP->GetWaypointRangeRingsStep() / 60.;
13045 radius *= 2; // Fudge factor
13046
13047 LLBBox radar_box;
13048 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13049 pWP->m_lat + radius, pWP->m_lon + radius);
13050 if (!BltBBox.IntersectOut(radar_box)) {
13051 RoutePointGui(*pWP).Draw(dc, this, NULL);
13052 }
13053 }
13054 }
13055 }
13056
13057 ++node;
13058 }
13059}
13060
13061void ChartCanvas::DrawBlinkObjects() {
13062 // All RoutePoints
13063 wxRect update_rect;
13064
13065 if (!pWayPointMan) return;
13066
13067 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13068 if (pWP) {
13069 if (pWP->m_bBlink) {
13070 update_rect.Union(pWP->CurrentRect_in_DC);
13071 }
13072 }
13073 }
13074 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13075}
13076
13077void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13078 // draw anchor watch rings, if activated
13079
13081 wxPoint r1, r2;
13082 wxPoint lAnchorPoint1, lAnchorPoint2;
13083 double lpp1 = 0.0;
13084 double lpp2 = 0.0;
13085 if (pAnchorWatchPoint1) {
13086 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13088 &lAnchorPoint1);
13089 }
13090 if (pAnchorWatchPoint2) {
13091 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13093 &lAnchorPoint2);
13094 }
13095
13096 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13097 wxPen ppPenr(GetGlobalColor("URED"), 2);
13098
13099 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13100 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13101 dc.SetBrush(*ppBrush);
13102
13103 if (lpp1 > 0) {
13104 dc.SetPen(ppPeng);
13105 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13106 }
13107
13108 if (lpp2 > 0) {
13109 dc.SetPen(ppPeng);
13110 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13111 }
13112
13113 if (lpp1 < 0) {
13114 dc.SetPen(ppPenr);
13115 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13116 }
13117
13118 if (lpp2 < 0) {
13119 dc.SetPen(ppPenr);
13120 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13121 }
13122 }
13123}
13124
13125double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13126 double lpp = 0.;
13127 wxPoint r1;
13128 wxPoint lAnchorPoint;
13129 double d1 = 0.0;
13130 double dabs;
13131 double tlat1, tlon1;
13132
13133 if (pAnchorWatchPoint) {
13134 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13135 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13136 dabs = fabs(d1 / 1852.);
13137 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13138 &tlat1, &tlon1);
13139 GetCanvasPointPix(tlat1, tlon1, &r1);
13140 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13141 &lAnchorPoint);
13142 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13143 pow((double)(lAnchorPoint.y - r1.y), 2));
13144
13145 // This is an entry watch
13146 if (d1 < 0) lpp = -lpp;
13147 }
13148 return lpp;
13149}
13150
13151//------------------------------------------------------------------------------------------
13152// Tides Support
13153//------------------------------------------------------------------------------------------
13154void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13155 if (!ptcmgr) return;
13156
13157 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13158
13159 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13160 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13161 double lon = pIDX->IDX_lon;
13162 double lat = pIDX->IDX_lat;
13163
13164 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13165 if ((type == 't') || (type == 'T')) {
13166 if (BBox.Contains(lat, lon)) {
13167 // Manage the point selection list
13168 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13169 }
13170 }
13171 }
13172}
13173
13174void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13175 if (!ptcmgr) return;
13176
13177 wxDateTime this_now = gTimeSource;
13178 bool cur_time = !gTimeSource.IsValid();
13179 if (cur_time) this_now = wxDateTime::Now();
13180 time_t t_this_now = this_now.GetTicks();
13181
13182 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13183 wxPENSTYLE_SOLID);
13184 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13185 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13186 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13187 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13188
13189 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13190 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13191 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13192 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13193 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13194 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13195
13196 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13197 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13198 int font_size = wxMax(10, dFont->GetPointSize());
13199 font_size /= g_Platform->GetDisplayDIPMult(this);
13200 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13201 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13202 false, dFont->GetFaceName());
13203
13204 dc.SetPen(*pblack_pen);
13205 dc.SetBrush(*pgreen_brush);
13206
13207 wxBitmap bm;
13208 switch (m_cs) {
13209 case GLOBAL_COLOR_SCHEME_DAY:
13210 bm = m_bmTideDay;
13211 break;
13212 case GLOBAL_COLOR_SCHEME_DUSK:
13213 bm = m_bmTideDusk;
13214 break;
13215 case GLOBAL_COLOR_SCHEME_NIGHT:
13216 bm = m_bmTideNight;
13217 break;
13218 default:
13219 bm = m_bmTideDay;
13220 break;
13221 }
13222
13223 int bmw = bm.GetWidth();
13224 int bmh = bm.GetHeight();
13225
13226 float scale_factor = 1.0;
13227
13228 // Set the onscreen size of the symbol
13229 // Compensate for various display resolutions
13230 float icon_pixelRefDim = 45;
13231
13232 // Tidal report graphic is scaled by the text size of the label in use
13233 wxScreenDC sdc;
13234 int height;
13235 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13236 height *= g_Platform->GetDisplayDIPMult(this);
13237 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13238
13239 scale_factor *= pix_factor;
13240
13241 float user_scale_factor = g_ChartScaleFactorExp;
13242 if (g_ChartScaleFactorExp > 1.0)
13243 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13244 1.2; // soften the scale factor a bit
13245
13246 scale_factor *= user_scale_factor;
13247 scale_factor *= GetContentScaleFactor();
13248
13249 {
13250 double marge = 0.05;
13251 std::vector<LLBBox> drawn_boxes;
13252 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13253 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13254
13255 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13256 if ((type == 't') || (type == 'T')) // only Tides
13257 {
13258 double lon = pIDX->IDX_lon;
13259 double lat = pIDX->IDX_lat;
13260
13261 if (BBox.ContainsMarge(lat, lon, marge)) {
13262 // Avoid drawing detailed graphic for duplicate tide stations
13263 if (GetVP().chart_scale < 500000) {
13264 bool bdrawn = false;
13265 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13266 if (drawn_boxes[i].Contains(lat, lon)) {
13267 bdrawn = true;
13268 break;
13269 }
13270 }
13271 if (bdrawn) continue; // the station loop
13272
13273 LLBBox this_box;
13274 this_box.Set(lat, lon, lat, lon);
13275 this_box.EnLarge(.005);
13276 drawn_boxes.push_back(this_box);
13277 }
13278
13279 wxPoint r;
13280 GetCanvasPointPix(lat, lon, &r);
13281 // draw standard icons
13282 if (GetVP().chart_scale > 500000) {
13283 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13284 }
13285 // draw "extended" icons
13286 else {
13287 dc.SetFont(*plabelFont);
13288 {
13289 {
13290 float val, nowlev;
13291 float ltleve = 0.;
13292 float htleve = 0.;
13293 time_t tctime;
13294 time_t lttime = 0;
13295 time_t httime = 0;
13296 bool wt;
13297 // define if flood or ebb in the last ten minutes and verify if
13298 // data are useable
13299 if (ptcmgr->GetTideFlowSens(
13300 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13301 pIDX->IDX_rec_num, nowlev, val, wt)) {
13302 // search forward the first HW or LW near "now" ( starting at
13303 // "now" - ten minutes )
13304 ptcmgr->GetHightOrLowTide(
13305 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13306 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13307 wt, pIDX->IDX_rec_num, val, tctime);
13308 if (wt) {
13309 httime = tctime;
13310 htleve = val;
13311 } else {
13312 lttime = tctime;
13313 ltleve = val;
13314 }
13315 wt = !wt;
13316
13317 // then search opposite tide near "now"
13318 if (tctime > t_this_now) // search backward
13319 ptcmgr->GetHightOrLowTide(
13320 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13321 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13322 pIDX->IDX_rec_num, val, tctime);
13323 else
13324 // or search forward
13325 ptcmgr->GetHightOrLowTide(
13326 t_this_now, FORWARD_TEN_MINUTES_STEP,
13327 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13328 val, tctime);
13329 if (wt) {
13330 httime = tctime;
13331 htleve = val;
13332 } else {
13333 lttime = tctime;
13334 ltleve = val;
13335 }
13336
13337 // draw the tide rectangle:
13338
13339 // tide icon rectangle has default pre-scaled width = 12 ,
13340 // height = 45
13341 int width = (int)(12 * scale_factor + 0.5);
13342 int height = (int)(45 * scale_factor + 0.5);
13343 int linew = wxMax(1, (int)(scale_factor));
13344 int xDraw = r.x - (width / 2);
13345 int yDraw = r.y - (height / 2);
13346
13347 // process tide state ( %height and flow sens )
13348 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13349 int hs = (httime > lttime) ? -4 : 4;
13350 hs *= (int)(scale_factor + 0.5);
13351 if (ts > 0.995 || ts < 0.005) hs = 0;
13352 int ht_y = (int)(height * ts);
13353
13354 // draw yellow tide rectangle outlined in black
13355 pblack_pen->SetWidth(linew);
13356 dc.SetPen(*pblack_pen);
13357 dc.SetBrush(*pyelo_brush);
13358 dc.DrawRectangle(xDraw, yDraw, width, height);
13359
13360 // draw blue rectangle as water height, smaller in width than
13361 // yellow rectangle
13362 dc.SetPen(*pblue_pen);
13363 dc.SetBrush(*pblue_brush);
13364 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13365 (width - (4 * linew)), height - ht_y);
13366
13367 // draw sens arrows (ensure they are not "under-drawn" by top
13368 // line of blue rectangle )
13369 int hl;
13370 wxPoint arrow[3];
13371 arrow[0].x = xDraw + 2 * linew;
13372 arrow[1].x = xDraw + width / 2;
13373 arrow[2].x = xDraw + width - 2 * linew;
13374 pyelo_pen->SetWidth(linew);
13375 pblue_pen->SetWidth(linew);
13376 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13377 {
13378 hl = (int)(height * 0.25) + yDraw;
13379 arrow[0].y = hl;
13380 arrow[1].y = hl + hs;
13381 arrow[2].y = hl;
13382 if (ts < 0.15)
13383 dc.SetPen(*pyelo_pen);
13384 else
13385 dc.SetPen(*pblue_pen);
13386 dc.DrawLines(3, arrow);
13387 }
13388 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13389 {
13390 hl = (int)(height * 0.5) + yDraw;
13391 arrow[0].y = hl;
13392 arrow[1].y = hl + hs;
13393 arrow[2].y = hl;
13394 if (ts < 0.40)
13395 dc.SetPen(*pyelo_pen);
13396 else
13397 dc.SetPen(*pblue_pen);
13398 dc.DrawLines(3, arrow);
13399 }
13400 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13401 {
13402 hl = (int)(height * 0.75) + yDraw;
13403 arrow[0].y = hl;
13404 arrow[1].y = hl + hs;
13405 arrow[2].y = hl;
13406 if (ts < 0.65)
13407 dc.SetPen(*pyelo_pen);
13408 else
13409 dc.SetPen(*pblue_pen);
13410 dc.DrawLines(3, arrow);
13411 }
13412 // draw tide level text
13413 wxString s;
13414 s.Printf("%3.1f", nowlev);
13415 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13416 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13417 int wx1;
13418 dc.GetTextExtent(s, &wx1, NULL);
13419 wx1 *= g_Platform->GetDisplayDIPMult(this);
13420 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13421 }
13422 }
13423 }
13424 }
13425 }
13426 }
13427 }
13428 }
13429}
13430
13431//------------------------------------------------------------------------------------------
13432// Currents Support
13433//------------------------------------------------------------------------------------------
13434
13435void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13436 if (!ptcmgr) return;
13437
13438 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13439
13440 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13441 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13442 double lon = pIDX->IDX_lon;
13443 double lat = pIDX->IDX_lat;
13444
13445 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13446 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13447 if ((BBox.Contains(lat, lon))) {
13448 // Manage the point selection list
13449 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13450 }
13451 }
13452 }
13453}
13454
13455void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13456 if (!ptcmgr) return;
13457
13458 float tcvalue, dir;
13459 bool bnew_val;
13460 char sbuf[20];
13461 wxFont *pTCFont;
13462 double lon_last = 0.;
13463 double lat_last = 0.;
13464 // arrow size for Raz Blanchard : 12 knots north
13465 double marge = 0.2;
13466 bool cur_time = !gTimeSource.IsValid();
13467
13468 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13469 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13470
13471 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13472 wxPENSTYLE_SOLID);
13473 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13474 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13475 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13476 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13477 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13478 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13479 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13480 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13481
13482 double skew_angle = GetVPRotation();
13483
13484 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13485 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13486 int font_size = wxMax(10, dFont->GetPointSize());
13487 font_size /= g_Platform->GetDisplayDIPMult(this);
13488 pTCFont = FontMgr::Get().FindOrCreateFont(
13489 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13490 false, dFont->GetFaceName());
13491
13492 float scale_factor = 1.0;
13493
13494 // Set the onscreen size of the symbol
13495 // Current report graphic is scaled by the text size of the label in use
13496 wxScreenDC sdc;
13497 int height;
13498 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13499 height *= g_Platform->GetDisplayDIPMult(this);
13500 float nominal_icon_size_pixels = 15;
13501 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13502
13503 scale_factor *= pix_factor;
13504
13505 float user_scale_factor = g_ChartScaleFactorExp;
13506 if (g_ChartScaleFactorExp > 1.0)
13507 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13508 1.2; // soften the scale factor a bit
13509
13510 scale_factor *= user_scale_factor;
13511
13512 scale_factor *= GetContentScaleFactor();
13513
13514 {
13515 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13516 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13517 double lon = pIDX->IDX_lon;
13518 double lat = pIDX->IDX_lat;
13519
13520 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13521 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13522 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13523 wxPoint r;
13524 GetCanvasPointPix(lat, lon, &r);
13525
13526 wxPoint d[4]; // points of a diamond at the current station location
13527 int dd = (int)(5.0 * scale_factor + 0.5);
13528 d[0].x = r.x;
13529 d[0].y = r.y + dd;
13530 d[1].x = r.x + dd;
13531 d[1].y = r.y;
13532 d[2].x = r.x;
13533 d[2].y = r.y - dd;
13534 d[3].x = r.x - dd;
13535 d[3].y = r.y;
13536
13537 if (1) {
13538 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13539 dc.SetPen(*pblack_pen);
13540 dc.SetBrush(*porange_brush);
13541 dc.DrawPolygon(4, d);
13542
13543 if (type == 'C') {
13544 dc.SetBrush(*pblack_brush);
13545 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13546 }
13547
13548 if (GetVP().chart_scale < 1000000) {
13549 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13550 continue;
13551 } else
13552 continue;
13553
13554 if (1 /*type == 'c'*/) {
13555 {
13556 // Get the display pixel location of the current station
13557 int pixxc, pixyc;
13558 pixxc = r.x;
13559 pixyc = r.y;
13560
13561 // Adjust drawing size using logarithmic scale. tcvalue is
13562 // current in knots
13563 double a1 = fabs(tcvalue) * 10.;
13564 // Current values <= 0.1 knot will have no arrow
13565 a1 = wxMax(1.0, a1);
13566 double a2 = log10(a1);
13567
13568 float cscale = scale_factor * a2 * 0.3;
13569
13570 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13571 dc.SetPen(*porange_pen);
13572 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13573 cscale);
13574 // Draw text, if enabled
13575
13576 if (bDrawCurrentValues) {
13577 dc.SetFont(*pTCFont);
13578 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13579 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13580 }
13581 }
13582 } // scale
13583 }
13584 /* This is useful for debugging the TC database
13585 else
13586 {
13587 dc.SetPen ( *porange_pen );
13588 dc.SetBrush ( *pgray_brush );
13589 dc.DrawPolygon ( 4, d );
13590 }
13591 */
13592 }
13593 lon_last = lon;
13594 lat_last = lat;
13595 }
13596 }
13597 }
13598}
13599
13600void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13601 ShowSingleTideDialog(x, y, pvIDX);
13602}
13603
13604void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13605 if (!pvIDX) return; // Validate input
13606
13607 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13608
13609 // Check if a tide dialog is already open and visible
13610 if (pCwin && pCwin->IsShown()) {
13611 // Same tide station: bring existing dialog to front (preserves user
13612 // context)
13613 if (pCwin->GetCurrentIDX() == pNewIDX) {
13614 pCwin->Raise();
13615 pCwin->SetFocus();
13616
13617 // Provide subtle visual feedback that dialog is already open
13618 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13619 return;
13620 }
13621
13622 // Different tide station: close current dialog before opening new one
13623 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13624 }
13625
13626 if (pCwin) {
13627 // This shouldn't happen but ensures clean state
13628 pCwin->Destroy();
13629 pCwin = NULL;
13630 }
13631
13632 // Create and display new tide dialog
13633 pCwin = new TCWin(this, x, y, pvIDX);
13634
13635 // Ensure the dialog is properly shown and focused
13636 if (pCwin) {
13637 pCwin->Show();
13638 pCwin->Raise();
13639 pCwin->SetFocus();
13640 }
13641}
13642
13643bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13644
13646 if (pCwin) {
13647 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13648 }
13649}
13650
13651#define NUM_CURRENT_ARROW_POINTS 9
13652static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13653 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13654 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13655 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13656
13657void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13658 double scale) {
13659 if (scale > 1e-2) {
13660 float sin_rot = sin(rot_angle * PI / 180.);
13661 float cos_rot = cos(rot_angle * PI / 180.);
13662
13663 // Move to the first point
13664
13665 float xt = CurrentArrowArray[0].x;
13666 float yt = CurrentArrowArray[0].y;
13667
13668 float xp = (xt * cos_rot) - (yt * sin_rot);
13669 float yp = (xt * sin_rot) + (yt * cos_rot);
13670 int x1 = (int)(xp * scale);
13671 int y1 = (int)(yp * scale);
13672
13673 // Walk thru the point list
13674 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13675 xt = CurrentArrowArray[ip].x;
13676 yt = CurrentArrowArray[ip].y;
13677
13678 float xp = (xt * cos_rot) - (yt * sin_rot);
13679 float yp = (xt * sin_rot) + (yt * cos_rot);
13680 int x2 = (int)(xp * scale);
13681 int y2 = (int)(yp * scale);
13682
13683 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13684
13685 x1 = x2;
13686 y1 = y2;
13687 }
13688 }
13689}
13690
13691wxString ChartCanvas::FindValidUploadPort() {
13692 wxString port;
13693 // Try to use the saved persistent upload port first
13694 if (!g_uploadConnection.IsEmpty() &&
13695 g_uploadConnection.StartsWith("Serial")) {
13696 port = g_uploadConnection;
13697 }
13698
13699 else {
13700 // If there is no persistent upload port recorded (yet)
13701 // then use the first available serial connection which has output defined.
13702 for (auto *cp : TheConnectionParams()) {
13703 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13704 port << "Serial:" << cp->Port;
13705 }
13706 }
13707 return port;
13708}
13709
13710void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13711 if (!win) return;
13712
13713 if (NULL == g_pais_query_dialog_active) {
13714 int pos_x = g_ais_query_dialog_x;
13715 int pos_y = g_ais_query_dialog_y;
13716
13717 if (g_pais_query_dialog_active) {
13718 g_pais_query_dialog_active->Destroy();
13719 g_pais_query_dialog_active = new AISTargetQueryDialog();
13720 } else {
13721 g_pais_query_dialog_active = new AISTargetQueryDialog();
13722 }
13723
13724 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13725 wxPoint(pos_x, pos_y));
13726
13727 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13728 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13729 g_pais_query_dialog_active->SetMMSI(mmsi);
13730 g_pais_query_dialog_active->UpdateText();
13731 wxSize sz = g_pais_query_dialog_active->GetSize();
13732
13733 bool b_reset_pos = false;
13734#ifdef __WXMSW__
13735 // Support MultiMonitor setups which an allow negative window positions.
13736 // If the requested window title bar does not intersect any installed
13737 // monitor, then default to simple primary monitor positioning.
13738 RECT frame_title_rect;
13739 frame_title_rect.left = pos_x;
13740 frame_title_rect.top = pos_y;
13741 frame_title_rect.right = pos_x + sz.x;
13742 frame_title_rect.bottom = pos_y + 30;
13743
13744 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13745 b_reset_pos = true;
13746#else
13747
13748 // Make sure drag bar (title bar) of window intersects wxClient Area of
13749 // screen, with a little slop...
13750 wxRect window_title_rect; // conservative estimate
13751 window_title_rect.x = pos_x;
13752 window_title_rect.y = pos_y;
13753 window_title_rect.width = sz.x;
13754 window_title_rect.height = 30;
13755
13756 wxRect ClientRect = wxGetClientDisplayRect();
13757 ClientRect.Deflate(
13758 60, 60); // Prevent the new window from being too close to the edge
13759 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13760
13761#endif
13762
13763 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13764
13765 } else {
13766 g_pais_query_dialog_active->SetMMSI(mmsi);
13767 g_pais_query_dialog_active->UpdateText();
13768 }
13769
13770 g_pais_query_dialog_active->Show();
13771}
13772
13773void ChartCanvas::ToggleCanvasQuiltMode() {
13774 bool cur_mode = GetQuiltMode();
13775
13776 if (!GetQuiltMode())
13777 SetQuiltMode(true);
13778 else if (GetQuiltMode()) {
13779 SetQuiltMode(false);
13780 g_sticky_chart = GetQuiltReferenceChartIndex();
13781 }
13782
13783 if (cur_mode != GetQuiltMode()) {
13784 SetupCanvasQuiltMode();
13785 DoCanvasUpdate();
13786 InvalidateGL();
13787 Refresh();
13788 }
13789 // TODO What to do about this?
13790 // g_bQuiltEnable = GetQuiltMode();
13791
13792 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13793 if (ps52plib) ps52plib->GenerateStateHash();
13794
13795 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13796 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13797}
13798
13799void ChartCanvas::DoCanvasStackDelta(int direction) {
13800 if (!GetQuiltMode()) {
13801 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13802 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13803 if ((current_stack_index + direction) < 0) return;
13804
13805 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13806 int new_dbIndex =
13807 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13808
13809 if (IsChartQuiltableRef(new_dbIndex)) {
13810 ToggleCanvasQuiltMode();
13811 SelectQuiltRefdbChart(new_dbIndex);
13812 m_bpersistent_quilt = false;
13813 }
13814 } else {
13815 SelectChartFromStack(current_stack_index + direction);
13816 }
13817 } else {
13818 std::vector<int> piano_chart_index_array =
13819 GetQuiltExtendedStackdbIndexArray();
13820 int refdb = GetQuiltRefChartdbIndex();
13821
13822 // Find the ref chart in the stack
13823 int current_index = -1;
13824 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13825 if (refdb == piano_chart_index_array[i]) {
13826 current_index = i;
13827 break;
13828 }
13829 }
13830 if (current_index == -1) return;
13831
13832 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13833 int target_family = ctet.GetChartFamily();
13834
13835 int new_index = -1;
13836 int check_index = current_index + direction;
13837 bool found = false;
13838 int check_dbIndex = -1;
13839 int new_dbIndex = -1;
13840
13841 // When quilted. switch within the same chart family
13842 while (!found &&
13843 (unsigned int)check_index < piano_chart_index_array.size() &&
13844 (check_index >= 0)) {
13845 check_dbIndex = piano_chart_index_array[check_index];
13846 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13847 if (target_family == cte.GetChartFamily()) {
13848 found = true;
13849 new_index = check_index;
13850 new_dbIndex = check_dbIndex;
13851 break;
13852 }
13853
13854 check_index += direction;
13855 }
13856
13857 if (!found) return;
13858
13859 if (!IsChartQuiltableRef(new_dbIndex)) {
13860 ToggleCanvasQuiltMode();
13861 SelectdbChart(new_dbIndex);
13862 m_bpersistent_quilt = true;
13863 } else {
13864 SelectQuiltRefChart(new_index);
13865 }
13866 }
13867
13868 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13869 // (checkmarks etc)
13870 SetQuiltChartHiLiteIndex(-1);
13871
13872 ReloadVP();
13873}
13874
13875//--------------------------------------------------------------------------------------------------------
13876//
13877// Toolbar support
13878//
13879//--------------------------------------------------------------------------------------------------------
13880
13881void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13882 // Handle the per-canvas toolbar clicks here
13883
13884 switch (event.GetId()) {
13885 case ID_ZOOMIN: {
13886 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13887 break;
13888 }
13889
13890 case ID_ZOOMOUT: {
13891 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13892 break;
13893 }
13894
13895 case ID_STKUP:
13896 DoCanvasStackDelta(1);
13897 DoCanvasUpdate();
13898 break;
13899
13900 case ID_STKDN:
13901 DoCanvasStackDelta(-1);
13902 DoCanvasUpdate();
13903 break;
13904
13905 case ID_FOLLOW: {
13906 TogglebFollow();
13907 break;
13908 }
13909
13910 case ID_CURRENT: {
13911 ShowCurrents(!GetbShowCurrent());
13912 ReloadVP();
13913 Refresh(false);
13914 break;
13915 }
13916
13917 case ID_TIDE: {
13918 ShowTides(!GetbShowTide());
13919 ReloadVP();
13920 Refresh(false);
13921 break;
13922 }
13923
13924 case ID_ROUTE: {
13925 if (0 == m_routeState) {
13926 StartRoute();
13927 } else {
13928 FinishRoute();
13929 }
13930
13931#ifdef __ANDROID__
13932 androidSetRouteAnnunciator(m_routeState == 1);
13933#endif
13934 break;
13935 }
13936
13937 case ID_AIS: {
13938 SetAISCanvasDisplayStyle(-1);
13939 break;
13940 }
13941
13942 default:
13943 break;
13944 }
13945
13946 // And then let gFrame handle the rest....
13947 event.Skip();
13948}
13949
13950void ChartCanvas::SetShowAIS(bool show) {
13951 m_bShowAIS = show;
13952 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13953 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13954}
13955
13956void ChartCanvas::SetAttenAIS(bool show) {
13957 m_bShowAISScaled = show;
13958 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13959 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13960}
13961
13962void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
13963 // make some arrays to hold the dfferences between cycle steps
13964 // show all, scaled, hide all
13965 bool bShowAIS_Array[3] = {true, true, false};
13966 bool bShowScaled_Array[3] = {false, true, true};
13967 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
13968 _("Attenuate less critical AIS targets"),
13969 _("Hide AIS Targets")};
13970 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
13971 int ArraySize = 3;
13972 int AIS_Toolbar_Switch = 0;
13973 if (StyleIndx == -1) { // -1 means coming from toolbar button
13974 // find current state of switch
13975 for (int i = 1; i < ArraySize; i++) {
13976 if ((bShowAIS_Array[i] == m_bShowAIS) &&
13977 (bShowScaled_Array[i] == m_bShowAISScaled))
13978 AIS_Toolbar_Switch = i;
13979 }
13980 AIS_Toolbar_Switch++; // we did click so continu with next item
13981 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
13982 AIS_Toolbar_Switch++;
13983
13984 } else { // coming from menu bar.
13985 AIS_Toolbar_Switch = StyleIndx;
13986 }
13987 // make sure we are not above array
13988 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
13989
13990 int AIS_Toolbar_Switch_Next =
13991 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
13992 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
13993 AIS_Toolbar_Switch_Next++;
13994 if (AIS_Toolbar_Switch_Next >= ArraySize)
13995 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
13996
13997 // Set found values to global and member variables
13998 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
13999 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14000}
14001
14002void ChartCanvas::TouchAISToolActive() {}
14003
14004void ChartCanvas::UpdateAISTBTool() {}
14005
14006//---------------------------------------------------------------------------------
14007//
14008// Compass/GPS status icon support
14009//
14010//---------------------------------------------------------------------------------
14011
14012void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14013 // Look for change in overlap or positions
14014 bool b_update = false;
14015 int cc1_edge_comp = 2;
14016 wxRect rect = m_Compass->GetRect();
14017 wxSize parent_size = GetSize();
14018
14019 parent_size *= m_displayScale;
14020
14021 // check to see if it would overlap if it was in its home position (upper
14022 // right)
14023 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14024 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14025 wxRect compass_rect(compass_pt, rect.GetSize());
14026
14027 m_Compass->Move(compass_pt);
14028
14029 if (m_Compass && m_Compass->IsShown())
14030 m_Compass->UpdateStatus(b_force_new | b_update);
14031
14032 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14033 scaler = wxMax(scaler, 1.0);
14034 wxPoint note_point = wxPoint(
14035 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14036 m_notification_button->Move(note_point);
14037 m_notification_button->UpdateStatus();
14038
14039 if (b_force_new | b_update) Refresh();
14040}
14041
14042void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14043 ChartTypeEnum New_Type,
14044 ChartFamilyEnum New_Family) {
14045 if (!GetpCurrentStack()) return;
14046 if (!ChartData) return;
14047
14048 if (index < GetpCurrentStack()->nEntry) {
14049 // Open the new chart
14050 ChartBase *pTentative_Chart;
14051 pTentative_Chart = ChartData->OpenStackChartConditional(
14052 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14053
14054 if (pTentative_Chart) {
14055 if (m_singleChart) m_singleChart->Deactivate();
14056
14057 m_singleChart = pTentative_Chart;
14058 m_singleChart->Activate();
14059
14060 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14061 GetpCurrentStack(), m_singleChart->GetFullPath());
14062 }
14063
14064 // Setup the view
14065 double zLat, zLon;
14066 if (m_bFollow) {
14067 zLat = gLat;
14068 zLon = gLon;
14069 } else {
14070 zLat = m_vLat;
14071 zLon = m_vLon;
14072 }
14073
14074 double best_scale_ppm = GetBestVPScale(m_singleChart);
14075 double rotation = GetVPRotation();
14076 double oldskew = GetVPSkew();
14077 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14078
14079 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14080 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14081 if (fabs(newskew) > 0.0001) rotation = newskew;
14082 }
14083
14084 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14085
14086 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14087 }
14088
14089 // refresh Piano
14090 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14091 if (idx < 0) return;
14092
14093 std::vector<int> piano_active_chart_index_array;
14094 piano_active_chart_index_array.push_back(
14095 GetpCurrentStack()->GetCurrentEntrydbIndex());
14096 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14097}
14098
14099void ChartCanvas::SelectdbChart(int dbindex) {
14100 if (!GetpCurrentStack()) return;
14101 if (!ChartData) return;
14102
14103 if (dbindex >= 0) {
14104 // Open the new chart
14105 ChartBase *pTentative_Chart;
14106 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14107
14108 if (pTentative_Chart) {
14109 if (m_singleChart) m_singleChart->Deactivate();
14110
14111 m_singleChart = pTentative_Chart;
14112 m_singleChart->Activate();
14113
14114 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14115 GetpCurrentStack(), m_singleChart->GetFullPath());
14116 }
14117
14118 // Setup the view
14119 double zLat, zLon;
14120 if (m_bFollow) {
14121 zLat = gLat;
14122 zLon = gLon;
14123 } else {
14124 zLat = m_vLat;
14125 zLon = m_vLon;
14126 }
14127
14128 double best_scale_ppm = GetBestVPScale(m_singleChart);
14129
14130 if (m_singleChart)
14131 SetViewPoint(zLat, zLon, best_scale_ppm,
14132 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14133
14134 // SetChartUpdatePeriod( );
14135
14136 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14137 }
14138
14139 // TODO refresh_Piano();
14140}
14141
14142void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14143 double target_scale = GetVP().view_scale_ppm;
14144
14145 if (!GetQuiltMode()) {
14146 if (GetpCurrentStack()) {
14147 int stack_index = -1;
14148 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14149 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14150 if (check_dbIndex < 0) continue;
14151 const ChartTableEntry &cte =
14152 ChartData->GetChartTableEntry(check_dbIndex);
14153 if (type == cte.GetChartType()) {
14154 stack_index = i;
14155 break;
14156 } else if (family == cte.GetChartFamily()) {
14157 stack_index = i;
14158 break;
14159 }
14160 }
14161
14162 if (stack_index >= 0) {
14163 SelectChartFromStack(stack_index);
14164 }
14165 }
14166 } else {
14167 int sel_dbIndex = -1;
14168 std::vector<int> piano_chart_index_array =
14169 GetQuiltExtendedStackdbIndexArray();
14170 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14171 int check_dbIndex = piano_chart_index_array[i];
14172 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14173 if (type == cte.GetChartType()) {
14174 if (IsChartQuiltableRef(check_dbIndex)) {
14175 sel_dbIndex = check_dbIndex;
14176 break;
14177 }
14178 } else if (family == cte.GetChartFamily()) {
14179 if (IsChartQuiltableRef(check_dbIndex)) {
14180 sel_dbIndex = check_dbIndex;
14181 break;
14182 }
14183 }
14184 }
14185
14186 if (sel_dbIndex >= 0) {
14187 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14188 // Re-qualify the quilt reference chart selection
14189 AdjustQuiltRefChart();
14190 }
14191
14192 // Now reset the scale to the target...
14193 SetVPScale(target_scale);
14194 }
14195
14196 SetQuiltChartHiLiteIndex(-1);
14197
14198 ReloadVP();
14199}
14200
14201bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14202 return std::find(m_tile_yesshow_index_array.begin(),
14203 m_tile_yesshow_index_array.end(),
14204 index) != m_tile_yesshow_index_array.end();
14205}
14206
14207bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14208 return std::find(m_tile_noshow_index_array.begin(),
14209 m_tile_noshow_index_array.end(),
14210 index) != m_tile_noshow_index_array.end();
14211}
14212
14213void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14214 if (std::find(m_tile_noshow_index_array.begin(),
14215 m_tile_noshow_index_array.end(),
14216 index) == m_tile_noshow_index_array.end()) {
14217 m_tile_noshow_index_array.push_back(index);
14218 }
14219}
14220
14221//-------------------------------------------------------------------------------------------------------
14222//
14223// Piano support
14224//
14225//-------------------------------------------------------------------------------------------------------
14226
14227void ChartCanvas::HandlePianoClick(
14228 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14229 if (g_options && g_options->IsShown())
14230 return; // Piano might be invalid due to chartset updates.
14231 if (!m_pCurrentStack) return;
14232 if (!ChartData) return;
14233
14234 // stop movement or on slow computer we may get something like :
14235 // zoom out with the wheel (timer is set)
14236 // quickly click and display a chart, which may zoom in
14237 // but the delayed timer fires first and it zooms out again!
14238 StopMovement();
14239
14240 // When switching by piano key click, we may appoint the new target chart to
14241 // be any chart in the composite array.
14242 // As an improvement to UX, find the chart that is "closest" to the current
14243 // vp,
14244 // and select that chart. This will cause a jump to the centroid of that
14245 // chart
14246
14247 double distance = 25000; // RTW
14248 int closest_index = -1;
14249 for (int chart_index : selected_dbIndex_array) {
14250 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14251 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14252 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14253
14254 // measure distance as Manhattan style
14255 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14256 if (test_distance < distance) {
14257 distance = test_distance;
14258 closest_index = chart_index;
14259 }
14260 }
14261
14262 int selected_dbIndex = selected_dbIndex_array[0];
14263 if (closest_index >= 0) selected_dbIndex = closest_index;
14264
14265 if (!GetQuiltMode()) {
14266 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14267 if (IsChartQuiltableRef(selected_dbIndex)) {
14268 ToggleCanvasQuiltMode();
14269 SelectQuiltRefdbChart(selected_dbIndex);
14270 m_bpersistent_quilt = false;
14271 } else {
14272 SelectChartFromStack(selected_index);
14273 }
14274 } else {
14275 SelectChartFromStack(selected_index);
14276 g_sticky_chart = selected_dbIndex;
14277 }
14278
14279 if (m_singleChart)
14280 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14281 } else {
14282 // Handle MBTiles overlays first
14283 // Left click simply toggles the noshow array index entry
14284 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14285 bool bfound = false;
14286 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14287 if (m_tile_noshow_index_array[i] ==
14288 selected_dbIndex) { // chart is in the noshow list
14289 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14290 i); // erase it
14291 bfound = true;
14292 break;
14293 }
14294 }
14295 if (!bfound) {
14296 m_tile_noshow_index_array.push_back(selected_dbIndex);
14297 }
14298
14299 // If not already present, add this tileset to the "yes_show" array.
14300 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14301 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14302 }
14303
14304 else {
14305 if (IsChartQuiltableRef(selected_dbIndex)) {
14306 // if( ChartData ) ChartData->PurgeCache();
14307
14308 // If the chart is a vector chart, and of very large scale,
14309 // then we had better set the new scale directly to avoid excessive
14310 // underzoom on, eg, Inland ENCs
14311 bool set_scale = false;
14312 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14313 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14314 set_scale = true;
14315 }
14316 }
14317
14318 if (!set_scale) {
14319 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14320 } else {
14321 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14322
14323 // Adjust scale so that the selected chart is underzoomed/overzoomed
14324 // by a controlled amount
14325 ChartBase *pc =
14326 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14327 if (pc) {
14328 double proposed_scale_onscreen =
14330
14331 if (g_bPreserveScaleOnX) {
14332 proposed_scale_onscreen =
14333 wxMin(proposed_scale_onscreen,
14334 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14335 GetCanvasWidth()));
14336 } else {
14337 proposed_scale_onscreen =
14338 wxMin(proposed_scale_onscreen,
14339 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14340 GetCanvasWidth()));
14341
14342 proposed_scale_onscreen =
14343 wxMax(proposed_scale_onscreen,
14344 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14346 }
14347
14348 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14349 }
14350 }
14351 } else {
14352 ToggleCanvasQuiltMode();
14353 SelectdbChart(selected_dbIndex);
14354 m_bpersistent_quilt = true;
14355 }
14356 }
14357 }
14358
14359 SetQuiltChartHiLiteIndex(-1);
14360 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14361 // (checkmarks etc)
14362 HideChartInfoWindow();
14363 DoCanvasUpdate();
14364 ReloadVP(); // Pick up the new selections
14365}
14366
14367void ChartCanvas::HandlePianoRClick(
14368 int x, int y, int selected_index,
14369 const std::vector<int> &selected_dbIndex_array) {
14370 if (g_options && g_options->IsShown())
14371 return; // Piano might be invalid due to chartset updates.
14372 if (!GetpCurrentStack()) return;
14373
14374 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14375 UpdateCanvasControlBar();
14376
14377 SetQuiltChartHiLiteIndex(-1);
14378}
14379
14380void ChartCanvas::HandlePianoRollover(
14381 int selected_index, const std::vector<int> &selected_dbIndex_array,
14382 int n_charts, int scale) {
14383 if (g_options && g_options->IsShown())
14384 return; // Piano might be invalid due to chartset updates.
14385 if (!GetpCurrentStack()) return;
14386 if (!ChartData) return;
14387
14388 if (ChartData->IsBusy()) return;
14389
14390 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14391
14392 if (!GetQuiltMode()) {
14393 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14394 } else {
14395 // Select the correct vector
14396 std::vector<int> piano_chart_index_array;
14397 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14398 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14399 if ((GetpCurrentStack()->nEntry > 1) ||
14400 (piano_chart_index_array.size() >= 1)) {
14401 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14402
14403 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14404 ReloadVP(false); // no VP adjustment allowed
14405 } else if (GetpCurrentStack()->nEntry == 1) {
14406 const ChartTableEntry &cte =
14407 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14408 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14409 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14410 ReloadVP(false);
14411 } else if ((-1 == selected_index) &&
14412 (0 == selected_dbIndex_array.size())) {
14413 ShowChartInfoWindow(key_location.x, -1);
14414 }
14415 }
14416 } else {
14417 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14418
14419 if ((GetpCurrentStack()->nEntry > 1) ||
14420 (piano_chart_index_array.size() >= 1)) {
14421 if (n_charts > 1)
14422 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14423 selected_dbIndex_array);
14424 else if (n_charts == 1)
14425 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14426
14427 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14428 ReloadVP(false); // no VP adjustment allowed
14429 }
14430 }
14431 }
14432}
14433
14434void ChartCanvas::ClearPianoRollover() {
14435 ClearQuiltChartHiLiteIndexArray();
14436 ShowChartInfoWindow(0, -1);
14437 std::vector<int> vec;
14438 ShowCompositeInfoWindow(0, 0, 0, vec);
14439 ReloadVP(false);
14440}
14441
14442void ChartCanvas::UpdateCanvasControlBar() {
14443 if (m_pianoFrozen) return;
14444
14445 if (!GetpCurrentStack()) return;
14446 if (!ChartData) return;
14447 if (!g_bShowChartBar) return;
14448
14449 int sel_type = -1;
14450 int sel_family = -1;
14451
14452 std::vector<int> piano_chart_index_array;
14453 std::vector<int> empty_piano_chart_index_array;
14454
14455 wxString old_hash = m_Piano->GetStoredHash();
14456
14457 if (GetQuiltMode()) {
14458 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14459 GetQuiltFullScreendbIndexArray());
14460
14461 std::vector<int> piano_active_chart_index_array =
14462 GetQuiltCandidatedbIndexArray();
14463 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14464
14465 std::vector<int> piano_eclipsed_chart_index_array =
14466 GetQuiltEclipsedStackdbIndexArray();
14467 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14468
14469 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14470 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14471
14472 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14473 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14474 } else {
14475 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14476 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14477 // TODO refresh_Piano();
14478
14479 if (m_singleChart) {
14480 sel_type = m_singleChart->GetChartType();
14481 sel_family = m_singleChart->GetChartFamily();
14482 }
14483 }
14484
14485 // Set up the TMerc and Skew arrays
14486 std::vector<int> piano_skew_chart_index_array;
14487 std::vector<int> piano_tmerc_chart_index_array;
14488 std::vector<int> piano_poly_chart_index_array;
14489
14490 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14491 const ChartTableEntry &ctei =
14492 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14493 double skew_norm = ctei.GetChartSkew();
14494 if (skew_norm > 180.) skew_norm -= 360.;
14495
14496 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14497 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14498
14499 // Polyconic skewed charts should show as skewed
14500 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14501 if (fabs(skew_norm) > 1.)
14502 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14503 else
14504 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14505 } else if (fabs(skew_norm) > 1.)
14506 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14507 }
14508 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14509 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14510 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14511
14512 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14513 if (new_hash != old_hash) {
14514 m_Piano->FormatKeys();
14515 HideChartInfoWindow();
14516 m_Piano->ResetRollover();
14517 SetQuiltChartHiLiteIndex(-1);
14518 m_brepaint_piano = true;
14519 }
14520
14521 // Create a bitmask int that describes what Family/Type of charts are shown in
14522 // the bar, and notify the platform.
14523 int mask = 0;
14524 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14525 const ChartTableEntry &ctei =
14526 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14527 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14528 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14529 if (e == CHART_FAMILY_RASTER) mask |= 1;
14530 if (e == CHART_FAMILY_VECTOR) {
14531 if (t == CHART_TYPE_CM93COMP)
14532 mask |= 4;
14533 else
14534 mask |= 2;
14535 }
14536 }
14537
14538 wxString s_indicated;
14539 if (sel_type == CHART_TYPE_CM93COMP)
14540 s_indicated = "cm93";
14541 else {
14542 if (sel_family == CHART_FAMILY_RASTER)
14543 s_indicated = "raster";
14544 else if (sel_family == CHART_FAMILY_VECTOR)
14545 s_indicated = "vector";
14546 }
14547
14548 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14549}
14550
14551void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14552
14553void ChartCanvas::PianoPopupMenu(
14554 int x, int y, int selected_index,
14555 const std::vector<int> &selected_dbIndex_array) {
14556 if (!GetpCurrentStack()) return;
14557
14558 // No context menu if quilting is disabled
14559 if (!GetQuiltMode()) return;
14560
14561 m_piano_ctx_menu = new wxMenu();
14562
14563 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14564 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14565 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14566 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14567 } else {
14568 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14569 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14570 // wxEVT_COMMAND_MENU_SELECTED,
14571 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14572
14573 menu_selected_dbIndex = selected_dbIndex_array[0];
14574 menu_selected_index = selected_index;
14575
14576 // Search the no-show array
14577 bool b_is_in_noshow = false;
14578 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14579 if (m_quilt_noshow_index_array[i] ==
14580 menu_selected_dbIndex) // chart is in the noshow list
14581 {
14582 b_is_in_noshow = true;
14583 break;
14584 }
14585 }
14586
14587 if (b_is_in_noshow) {
14588 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14589 _("Show This Chart"));
14590 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14591 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14592 } else if (GetpCurrentStack()->nEntry > 1) {
14593 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14594 _("Hide This Chart"));
14595 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14596 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14597 }
14598 }
14599
14600 wxPoint pos = wxPoint(x, y - 30);
14601
14602 // Invoke the drop-down menu
14603 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14604 PopupMenu(m_piano_ctx_menu, pos);
14605
14606 delete m_piano_ctx_menu;
14607 m_piano_ctx_menu = NULL;
14608
14609 HideChartInfoWindow();
14610 m_Piano->ResetRollover();
14611
14612 SetQuiltChartHiLiteIndex(-1);
14613 ClearQuiltChartHiLiteIndexArray();
14614
14615 ReloadVP();
14616}
14617
14618void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14619 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14620 if (m_quilt_noshow_index_array[i] ==
14621 menu_selected_dbIndex) // chart is in the noshow list
14622 {
14623 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14624 break;
14625 }
14626 }
14627}
14628
14629void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14630 if (!GetpCurrentStack()) return;
14631 if (!ChartData) return;
14632
14633 RemoveChartFromQuilt(menu_selected_dbIndex);
14634
14635 // It could happen that the chart being disabled is the reference
14636 // chart....
14637 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14638 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14639
14640 int i = menu_selected_index + 1; // select next smaller scale chart
14641 bool b_success = false;
14642 while (i < GetpCurrentStack()->nEntry - 1) {
14643 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14644 if (type == ChartData->GetDBChartType(dbIndex)) {
14645 SelectQuiltRefChart(i);
14646 b_success = true;
14647 break;
14648 }
14649 i++;
14650 }
14651
14652 // If that did not work, try to select the next larger scale compatible
14653 // chart
14654 if (!b_success) {
14655 i = menu_selected_index - 1;
14656 while (i > 0) {
14657 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14658 if (type == ChartData->GetDBChartType(dbIndex)) {
14659 SelectQuiltRefChart(i);
14660 b_success = true;
14661 break;
14662 }
14663 i--;
14664 }
14665 }
14666 }
14667}
14668
14669void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14670 // Remove the item from the list (if it appears) to avoid multiple addition
14671 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14672 if (m_quilt_noshow_index_array[i] ==
14673 dbIndex) // chart is already in the noshow list
14674 {
14675 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14676 break;
14677 }
14678 }
14679
14680 m_quilt_noshow_index_array.push_back(dbIndex);
14681}
14682
14683bool ChartCanvas::UpdateS52State() {
14684 bool retval = false;
14685
14686 if (ps52plib) {
14687 ps52plib->SetShowS57Text(m_encShowText);
14688 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14689 ps52plib->m_bShowSoundg = m_encShowDepth;
14690 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14691 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14692
14693 // Lights
14694 if (!m_encShowLights) // On, going off
14695 ps52plib->AddObjNoshow("LIGHTS");
14696 else // Off, going on
14697 ps52plib->RemoveObjNoshow("LIGHTS");
14698 ps52plib->SetLightsOff(!m_encShowLights);
14699 ps52plib->m_bExtendLightSectors = true;
14700
14701 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14702 ps52plib->SetAnchorOn(m_encShowAnchor);
14703 ps52plib->SetQualityOfData(m_encShowDataQual);
14704 }
14705
14706 return retval;
14707}
14708
14709void ChartCanvas::SetShowENCDataQual(bool show) {
14710 m_encShowDataQual = show;
14711 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14712 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14713
14714 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14715}
14716
14717void ChartCanvas::SetShowENCText(bool show) {
14718 m_encShowText = show;
14719 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14720 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14721
14722 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14723}
14724
14725void ChartCanvas::SetENCDisplayCategory(int category) {
14726 m_encDisplayCategory = category;
14727 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14728}
14729
14730void ChartCanvas::SetShowENCDepth(bool show) {
14731 m_encShowDepth = show;
14732 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14733 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14734
14735 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14736}
14737
14738void ChartCanvas::SetShowENCLightDesc(bool show) {
14739 m_encShowLightDesc = show;
14740 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14741 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14742
14743 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14744}
14745
14746void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14747 m_encShowBuoyLabels = show;
14748 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14749}
14750
14751void ChartCanvas::SetShowENCLights(bool show) {
14752 m_encShowLights = show;
14753 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14754 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14755
14756 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14757}
14758
14759void ChartCanvas::SetShowENCAnchor(bool show) {
14760 m_encShowAnchor = show;
14761 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14762 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14763
14764 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14765}
14766
14767wxRect ChartCanvas::GetMUIBarRect() {
14768 wxRect rv;
14769 if (m_muiBar) {
14770 rv = m_muiBar->GetRect();
14771 }
14772
14773 return rv;
14774}
14775
14776void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14777 if (!GetAlertString().IsEmpty()) {
14778 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14779 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14780
14781 dc.SetFont(*pfont);
14782 dc.SetPen(*wxTRANSPARENT_PEN);
14783
14784 dc.SetBrush(wxColour(243, 229, 47));
14785 int w, h;
14786 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14787 h += 2;
14788 // int yp = vp.pix_height - 20 - h;
14789
14790 wxRect sbr = GetScaleBarRect();
14791 int xp = sbr.x + sbr.width + 10;
14792 int yp = (sbr.y + sbr.height) - h;
14793
14794 int wdraw = w + 10;
14795 dc.DrawRectangle(xp, yp, wdraw, h);
14796 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14797 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14798 }
14799}
14800
14801//--------------------------------------------------------------------------------------------------------
14802// Screen Brightness Control Support Routines
14803//
14804//--------------------------------------------------------------------------------------------------------
14805
14806#ifdef __UNIX__
14807#define BRIGHT_XCALIB
14808#define __OPCPN_USEICC__
14809#endif
14810
14811#ifdef __OPCPN_USEICC__
14812int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14813 double co_green, double co_blue);
14814
14815wxString temp_file_name;
14816#endif
14817
14818#if 0
14819class ocpnCurtain: public wxDialog
14820{
14821 DECLARE_CLASS( ocpnCurtain )
14822 DECLARE_EVENT_TABLE()
14823
14824public:
14825 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14826 ~ocpnCurtain( );
14827 bool ProcessEvent(wxEvent& event);
14828
14829};
14830
14831IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14832
14833BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14834END_EVENT_TABLE()
14835
14836ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14837{
14838 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14839}
14840
14841ocpnCurtain::~ocpnCurtain()
14842{
14843}
14844
14845bool ocpnCurtain::ProcessEvent(wxEvent& event)
14846{
14847 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14848 return GetParent()->GetEventHandler()->ProcessEvent(event);
14849}
14850#endif
14851
14852#ifdef _WIN32
14853#include <windows.h>
14854
14855HMODULE hGDI32DLL;
14856typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14857typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14858SetDeviceGammaRamp_ptr_type
14859 g_pSetDeviceGammaRamp; // the API entry points in the dll
14860GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14861
14862WORD *g_pSavedGammaMap;
14863
14864#endif
14865
14866int InitScreenBrightness() {
14867#ifdef _WIN32
14868#ifdef ocpnUSE_GL
14869 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14870 HDC hDC;
14871 BOOL bbr;
14872
14873 if (NULL == hGDI32DLL) {
14874 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14875
14876 if (NULL != hGDI32DLL) {
14877 // Get the entry points of the required functions
14878 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14879 hGDI32DLL, "SetDeviceGammaRamp");
14880 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14881 hGDI32DLL, "GetDeviceGammaRamp");
14882
14883 // If the functions are not found, unload the DLL and return false
14884 if ((NULL == g_pSetDeviceGammaRamp) ||
14885 (NULL == g_pGetDeviceGammaRamp)) {
14886 FreeLibrary(hGDI32DLL);
14887 hGDI32DLL = NULL;
14888 return 0;
14889 }
14890 }
14891 }
14892
14893 // Interface is ready, so....
14894 // Get some storage
14895 if (!g_pSavedGammaMap) {
14896 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14897
14898 hDC = GetDC(NULL); // Get the full screen DC
14899 bbr = g_pGetDeviceGammaRamp(
14900 hDC, g_pSavedGammaMap); // Get the existing ramp table
14901 ReleaseDC(NULL, hDC); // Release the DC
14902 }
14903
14904 // On Windows hosts, try to adjust the registry to allow full range
14905 // setting of Gamma table This is an undocumented Windows hack.....
14906 wxRegKey *pRegKey = new wxRegKey(
14907 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
14908 "NT\\CurrentVersion\\ICM");
14909 if (!pRegKey->Exists()) pRegKey->Create();
14910 pRegKey->SetValue("GdiIcmGammaRange", 256);
14911
14912 g_brightness_init = true;
14913 return 1;
14914 }
14915#endif
14916
14917 {
14918 if (NULL == g_pcurtain) {
14919 if (gFrame->CanSetTransparent()) {
14920 // Build the curtain window
14921 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, "",
14922 wxPoint(0, 0), ::wxGetDisplaySize(),
14923 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14924 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14925
14926 // g_pcurtain = new ocpnCurtain(gFrame,
14927 // wxPoint(0,0),::wxGetDisplaySize(),
14928 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14929 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14930
14931 g_pcurtain->Hide();
14932
14933 HWND hWnd = GetHwndOf(g_pcurtain);
14934 SetWindowLong(hWnd, GWL_EXSTYLE,
14935 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14936 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14937 g_pcurtain->SetTransparent(0);
14938
14939 g_pcurtain->Maximize();
14940 g_pcurtain->Show();
14941
14942 // All of this is obtuse, but necessary for Windows...
14943 g_pcurtain->Enable();
14944 g_pcurtain->Disable();
14945
14946 gFrame->Disable();
14947 gFrame->Enable();
14948 // SetFocus();
14949 }
14950 }
14951 g_brightness_init = true;
14952
14953 return 1;
14954 }
14955#else
14956 // Look for "xcalib" application
14957 wxString cmd("xcalib -version");
14958
14959 wxArrayString output;
14960 long r = wxExecute(cmd, output);
14961 if (0 != r)
14962 wxLogMessage(
14963 " External application \"xcalib\" not found. Screen brightness "
14964 "not changed.");
14965
14966 g_brightness_init = true;
14967 return 0;
14968#endif
14969}
14970
14971int RestoreScreenBrightness() {
14972#ifdef _WIN32
14973
14974 if (g_pSavedGammaMap) {
14975 HDC hDC = GetDC(NULL); // Get the full screen DC
14976 g_pSetDeviceGammaRamp(hDC,
14977 g_pSavedGammaMap); // Restore the saved ramp table
14978 ReleaseDC(NULL, hDC); // Release the DC
14979
14980 free(g_pSavedGammaMap);
14981 g_pSavedGammaMap = NULL;
14982 }
14983
14984 if (g_pcurtain) {
14985 g_pcurtain->Close();
14986 g_pcurtain->Destroy();
14987 g_pcurtain = NULL;
14988 }
14989
14990 g_brightness_init = false;
14991 return 1;
14992
14993#endif
14994
14995#ifdef BRIGHT_XCALIB
14996 if (g_brightness_init) {
14997 wxString cmd;
14998 cmd = "xcalib -clear";
14999 wxExecute(cmd, wxEXEC_ASYNC);
15000 g_brightness_init = false;
15001 }
15002
15003 return 1;
15004#endif
15005
15006 return 0;
15007}
15008
15009// Set brightness. [0..100]
15010int SetScreenBrightness(int brightness) {
15011#ifdef _WIN32
15012
15013 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15014 // some (most modern?) versions of gdi32.dll Load the required library dll,
15015 // if not already in place
15016#ifdef ocpnUSE_GL
15017 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
15018 if (g_pcurtain) {
15019 g_pcurtain->Close();
15020 g_pcurtain->Destroy();
15021 g_pcurtain = NULL;
15022 }
15023
15024 InitScreenBrightness();
15025
15026 if (NULL == hGDI32DLL) {
15027 // Unicode stuff.....
15028 wchar_t wdll_name[80];
15029 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15030 LPCWSTR cstr = wdll_name;
15031
15032 hGDI32DLL = LoadLibrary(cstr);
15033
15034 if (NULL != hGDI32DLL) {
15035 // Get the entry points of the required functions
15036 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15037 hGDI32DLL, "SetDeviceGammaRamp");
15038 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15039 hGDI32DLL, "GetDeviceGammaRamp");
15040
15041 // If the functions are not found, unload the DLL and return false
15042 if ((NULL == g_pSetDeviceGammaRamp) ||
15043 (NULL == g_pGetDeviceGammaRamp)) {
15044 FreeLibrary(hGDI32DLL);
15045 hGDI32DLL = NULL;
15046 return 0;
15047 }
15048 }
15049 }
15050
15051 HDC hDC = GetDC(NULL); // Get the full screen DC
15052
15053 /*
15054 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15055 if (cmcap != CM_GAMMA_RAMP)
15056 {
15057 wxLogMessage(" Video hardware does not support brightness control by
15058 gamma ramp adjustment."); return false;
15059 }
15060 */
15061
15062 int increment = brightness * 256 / 100;
15063
15064 // Build the Gamma Ramp table
15065 WORD GammaTable[3][256];
15066
15067 int table_val = 0;
15068 for (int i = 0; i < 256; i++) {
15069 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15070 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15071 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15072
15073 table_val += increment;
15074
15075 if (table_val > 65535) table_val = 65535;
15076 }
15077
15078 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15079 ReleaseDC(NULL, hDC); // Release the DC
15080
15081 return 1;
15082 }
15083#endif
15084
15085 {
15086 if (g_pSavedGammaMap) {
15087 HDC hDC = GetDC(NULL); // Get the full screen DC
15088 g_pSetDeviceGammaRamp(hDC,
15089 g_pSavedGammaMap); // Restore the saved ramp table
15090 ReleaseDC(NULL, hDC); // Release the DC
15091 }
15092
15093 if (brightness < 100) {
15094 if (NULL == g_pcurtain) InitScreenBrightness();
15095
15096 if (g_pcurtain) {
15097 int sbrite = wxMax(1, brightness);
15098 sbrite = wxMin(100, sbrite);
15099
15100 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15101 }
15102 } else {
15103 if (g_pcurtain) {
15104 g_pcurtain->Close();
15105 g_pcurtain->Destroy();
15106 g_pcurtain = NULL;
15107 }
15108 }
15109
15110 return 1;
15111 }
15112
15113#endif
15114
15115#ifdef BRIGHT_XCALIB
15116
15117 if (!g_brightness_init) {
15118 last_brightness = 100;
15119 g_brightness_init = true;
15120 temp_file_name = wxFileName::CreateTempFileName("");
15121 InitScreenBrightness();
15122 }
15123
15124#ifdef __OPCPN_USEICC__
15125 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15126 // desired, and then activate this temporary profile using xcalib <filename>
15127 if (!CreateSimpleICCProfileFile(
15128 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15129 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15130 wxString cmd("xcalib ");
15131 cmd += temp_file_name;
15132
15133 wxExecute(cmd, wxEXEC_ASYNC);
15134 }
15135
15136#else
15137 // Or, use "xcalib -co" to set overall contrast value
15138 // This is not as nice, since the -co parameter wants to be a fraction of
15139 // the current contrast, and values greater than 100 are not allowed. As a
15140 // result, increases of contrast must do a "-clear" step first, which
15141 // produces objectionable flashing.
15142 if (brightness > last_brightness) {
15143 wxString cmd;
15144 cmd = "xcalib -clear";
15145 wxExecute(cmd, wxEXEC_ASYNC);
15146
15147 ::wxMilliSleep(10);
15148
15149 int brite_adj = wxMax(1, brightness);
15150 cmd.Printf("xcalib -co %2d -a", brite_adj);
15151 wxExecute(cmd, wxEXEC_ASYNC);
15152 } else {
15153 int brite_adj = wxMax(1, brightness);
15154 int factor = (brite_adj * 100) / last_brightness;
15155 factor = wxMax(1, factor);
15156 wxString cmd;
15157 cmd.Printf("xcalib -co %2d -a", factor);
15158 wxExecute(cmd, wxEXEC_ASYNC);
15159 }
15160
15161#endif
15162
15163 last_brightness = brightness;
15164
15165#endif
15166
15167 return 0;
15168}
15169
15170#ifdef __OPCPN_USEICC__
15171
15172#define MLUT_TAG 0x6d4c5554L
15173#define VCGT_TAG 0x76636774L
15174
15175int GetIntEndian(unsigned char *s) {
15176 int ret;
15177 unsigned char *p;
15178 int i;
15179
15180 p = (unsigned char *)&ret;
15181
15182 if (1)
15183 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15184 else
15185 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15186
15187 return ret;
15188}
15189
15190unsigned short GetShortEndian(unsigned char *s) {
15191 unsigned short ret;
15192 unsigned char *p;
15193 int i;
15194
15195 p = (unsigned char *)&ret;
15196
15197 if (1)
15198 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15199 else
15200 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15201
15202 return ret;
15203}
15204
15205// Create a very simple Gamma correction file readable by xcalib
15206int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15207 double co_green, double co_blue) {
15208 FILE *fp;
15209
15210 if (file_name) {
15211 fp = fopen(file_name, "wb");
15212 if (!fp) return -1; /* file can not be created */
15213 } else
15214 return -1; /* filename char pointer not valid */
15215
15216 // Write header
15217 char header[128];
15218 for (int i = 0; i < 128; i++) header[i] = 0;
15219
15220 fwrite(header, 128, 1, fp);
15221
15222 // Num tags
15223 int numTags0 = 1;
15224 int numTags = GetIntEndian((unsigned char *)&numTags0);
15225 fwrite(&numTags, 1, 4, fp);
15226
15227 int tagName0 = VCGT_TAG;
15228 int tagName = GetIntEndian((unsigned char *)&tagName0);
15229 fwrite(&tagName, 1, 4, fp);
15230
15231 int tagOffset0 = 128 + 4 * sizeof(int);
15232 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15233 fwrite(&tagOffset, 1, 4, fp);
15234
15235 int tagSize0 = 1;
15236 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15237 fwrite(&tagSize, 1, 4, fp);
15238
15239 fwrite(&tagName, 1, 4, fp); // another copy of tag
15240
15241 fwrite(&tagName, 1, 4, fp); // dummy
15242
15243 // Table type
15244
15245 /* VideoCardGammaTable (The simplest type) */
15246 int gammatype0 = 0;
15247 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15248 fwrite(&gammatype, 1, 4, fp);
15249
15250 int numChannels0 = 3;
15251 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15252 fwrite(&numChannels, 1, 2, fp);
15253
15254 int numEntries0 = 256;
15255 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15256 fwrite(&numEntries, 1, 2, fp);
15257
15258 int entrySize0 = 1;
15259 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15260 fwrite(&entrySize, 1, 2, fp);
15261
15262 unsigned char ramp[256];
15263
15264 // Red ramp
15265 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15266 fwrite(ramp, 256, 1, fp);
15267
15268 // Green ramp
15269 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15270 fwrite(ramp, 256, 1, fp);
15271
15272 // Blue ramp
15273 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15274 fwrite(ramp, 256, 1, fp);
15275
15276 fclose(fp);
15277
15278 return 0;
15279}
15280#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:1296
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1295
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1296
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1295
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:13645
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:4495
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4491
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3589
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13604
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:4441
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:781
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:484
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:515
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2324
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:7991
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7801
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5023
float GetVPScale()
Return the ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:472
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:872
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4572
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5303
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:765
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4516
bool IsTideDialogOpen() const
Definition chcanv.cpp:13643
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:4578
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13600
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4436
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5322
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10121
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:440
wxColour GetFontColor(const wxString &TextElement) const
Gets the text color for a UI element.
Definition font_mgr.cpp:108
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Get a font object for a UI element.
Definition font_mgr.cpp:191
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:811
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:121
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:133
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:124
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:52
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.
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:76
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:178
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.