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 "model/ais_decoder.h"
41#include "model/ais_target_data.h"
42#include "model/cmdline.h"
43#include "model/conn_params.h"
44#include "model/geodesic.h"
45#include "model/gui.h"
46#include "model/gui_vars.h"
47#include "model/idents.h"
48#include "model/multiplexer.h"
50#include "model/nav_object_database.h"
51#include "model/navobj_db.h"
52#include "model/navutil_base.h"
53#include "model/own_ship.h"
54#include "model/plugin_comm.h"
55#include "model/route.h"
56#include "model/routeman.h"
57#include "model/select.h"
58#include "model/select_item.h"
59#include "model/track.h"
60
61#include "ais.h"
64#include "canvas_config.h"
65#include "canvas_menu.h"
66#include "canvas_options.h"
67#include "chartdb.h"
68#include "chartimg.h"
69#include "chcanv.h"
70#include "ch_info_win.h"
71#include "cm93.h" // for chart outline draw
72#include "compass.h"
73#include "concanv.h"
74#include "detail_slider.h"
75#include "hotkeys_dlg.h"
76#include "font_mgr.h"
77#include "glTextureDescriptor.h"
78#include "GoToPositionDialog.h"
79#include "gshhs.h"
80#include "iENCToolbar.h"
81#include "kml.h"
82#include "line_clip.h"
83#include "MarkInfo.h"
84#include "mbtiles.h"
85#include "MUIBar.h"
86#include "navutil.h"
87#include "OCPN_AUIManager.h"
88#include "ocpndc.h"
89#include "ocpn_frame.h"
90#include "ocpn_pixel.h"
91#include "OCPNRegion.h"
92#include "options.h"
93#include "piano.h"
94#include "pluginmanager.h"
95#include "Quilt.h"
96#include "route_gui.h"
97#include "routemanagerdialog.h"
98#include "route_point_gui.h"
99#include "RoutePropDlgImpl.h"
100#include "s52plib.h"
101#include "s52utils.h"
102#include "S57QueryDialog.h"
103#include "s57chart.h" // for ArrayOfS57Obj
104#include "shapefile_basemap.h"
105#include "styles.h"
106#include "SystemCmdSound.h"
107#include "tcmgr.h"
108#include "TCWin.h"
109#include "thumbwin.h"
110#include "tide_time.h"
111#include "timers.h"
112#include "toolbar.h"
113#include "track_gui.h"
114#include "TrackPropDlg.h"
115#include "undo.h"
116
117#include "s57_ocpn_utils.h"
118
119#ifdef __ANDROID__
120#include "androidUTIL.h"
121#endif
122
123#ifdef ocpnUSE_GL
124#include "glChartCanvas.h"
125#include "notification_manager_gui.h"
127#endif
128
129#ifdef __VISUALC__
130#include <wx/msw/msvcrt.h>
131#endif
132
133#ifndef __WXMSW__
134#include <signal.h>
135#include <setjmp.h>
136#endif
137
138#ifdef __WXMSW__
139#define printf printf2
140
141int __cdecl printf2(const char *format, ...) {
142 char str[1024];
143
144 va_list argptr;
145 va_start(argptr, format);
146 int ret = vsnprintf(str, sizeof(str), format, argptr);
147 va_end(argptr);
148 OutputDebugStringA(str);
149 return ret;
150}
151#endif
152
153#if defined(__MSVC__) && (_MSC_VER < 1700)
154#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
155#endif
156
157// Define to enable the invocation of a temporary menubar by pressing the Alt
158// key. Not implemented for Windows XP, as it interferes with Alt-Tab
159// processing.
160#define OCPN_ALT_MENUBAR 1
161
162// Profiling support
163// #include "/usr/include/valgrind/callgrind.h"
164
165// ----------------------------------------------------------------------------
166// Useful Prototypes
167// ----------------------------------------------------------------------------
168extern ColorScheme global_color_scheme; // library dependence
169extern wxColor GetDimColor(wxColor c); // library dependence
170
171static bool g_bSmoothRecenter = true;
172static bool bDrawCurrentValues;
182static int mouse_x;
192static int mouse_y;
193static bool mouse_leftisdown;
194static bool g_brouteCreating;
195static int r_gamma_mult;
196static int g_gamma_mult;
197static int b_gamma_mult;
198static int gamma_state;
199static bool g_brightness_init;
200static int last_brightness;
201static wxGLContext *g_pGLcontext; // shared common context
202
203// "Curtain" mode parameters
204static wxDialog *g_pcurtain;
205
206static wxString g_lastS52PLIBPluginMessage;
207
208#define MIN_BRIGHT 10
209#define MAX_BRIGHT 100
210
211//------------------------------------------------------------------------------
212// ChartCanvas Implementation
213//------------------------------------------------------------------------------
214BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
215EVT_PAINT(ChartCanvas::OnPaint)
216EVT_ACTIVATE(ChartCanvas::OnActivate)
217EVT_SIZE(ChartCanvas::OnSize)
218#ifndef HAVE_WX_GESTURE_EVENTS
219EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
220#endif
221EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
222EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
223EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
224EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
225EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
226EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
227EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
228EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
229EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
230EVT_KEY_UP(ChartCanvas::OnKeyUp)
231EVT_CHAR(ChartCanvas::OnKeyChar)
232EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
233EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
234EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
235EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
236EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
237EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
238EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
239EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
240
241END_EVENT_TABLE()
242
243// Define a constructor for my canvas
244ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
245 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
246 m_nmea_log(nmea_log) {
247 parent_frame = (MyFrame *)frame; // save a pointer to parent
248 m_canvasIndex = canvasIndex;
249
250 pscratch_bm = NULL;
251
252 SetBackgroundColour(wxColour(0, 0, 0));
253 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
254 // color scheme change
255
256 m_groupIndex = 0;
257 m_bDrawingRoute = false;
258 m_bRouteEditing = false;
259 m_bMarkEditing = false;
260 m_bRoutePoinDragging = false;
261 m_bIsInRadius = false;
262 m_bMayToggleMenuBar = true;
263
264 m_bFollow = false;
265 m_bShowNavobjects = true;
266 m_bTCupdate = false;
267 m_bAppendingRoute = false; // was true in MSW, why??
268 pThumbDIBShow = NULL;
269 m_bShowCurrent = false;
270 m_bShowTide = false;
271 bShowingCurrent = false;
272 pCwin = NULL;
273 warp_flag = false;
274 m_bzooming = false;
275 m_b_paint_enable = true;
276 m_routeState = 0;
277
278 pss_overlay_bmp = NULL;
279 pss_overlay_mask = NULL;
280 m_bChartDragging = false;
281 m_bMeasure_Active = false;
282 m_bMeasure_DistCircle = false;
283 m_pMeasureRoute = NULL;
284 m_pTrackRolloverWin = NULL;
285 m_pRouteRolloverWin = NULL;
286 m_pAISRolloverWin = NULL;
287 m_bedge_pan = false;
288 m_disable_edge_pan = false;
289 m_dragoffsetSet = false;
290 m_bautofind = false;
291 m_bFirstAuto = true;
292 m_groupIndex = 0;
293 m_singleChart = NULL;
294 m_upMode = NORTH_UP_MODE;
295 m_bShowAIS = true;
296 m_bShowAISScaled = false;
297 m_timed_move_vp_active = false;
298
299 m_vLat = 0.;
300 m_vLon = 0.;
301
302 m_pCIWin = NULL;
303
304 m_pSelectedRoute = NULL;
305 m_pSelectedTrack = NULL;
306 m_pRoutePointEditTarget = NULL;
307 m_pFoundPoint = NULL;
308 m_pMouseRoute = NULL;
309 m_prev_pMousePoint = NULL;
310 m_pEditRouteArray = NULL;
311 m_pFoundRoutePoint = NULL;
312 m_FinishRouteOnKillFocus = true;
313
314 m_pRolloverRouteSeg = NULL;
315 m_pRolloverTrackSeg = NULL;
316 m_bsectors_shown = false;
317
318 m_bbrightdir = false;
319 r_gamma_mult = 1;
320 g_gamma_mult = 1;
321 b_gamma_mult = 1;
322
323 m_pos_image_user_day = NULL;
324 m_pos_image_user_dusk = NULL;
325 m_pos_image_user_night = NULL;
326 m_pos_image_user_grey_day = NULL;
327 m_pos_image_user_grey_dusk = NULL;
328 m_pos_image_user_grey_night = NULL;
329
330 m_zoom_factor = 1;
331 m_rotation_speed = 0;
332 m_mustmove = 0;
333
334 m_OSoffsetx = 0.;
335 m_OSoffsety = 0.;
336
337 m_pos_image_user_yellow_day = NULL;
338 m_pos_image_user_yellow_dusk = NULL;
339 m_pos_image_user_yellow_night = NULL;
340
341 SetOwnShipState(SHIP_INVALID);
342
343 undo = new Undo(this);
344
345 VPoint.Invalidate();
346
347 m_glcc = NULL;
348
349 m_focus_indicator_pix = 1;
350
351 m_pCurrentStack = NULL;
352 m_bpersistent_quilt = false;
353 m_piano_ctx_menu = NULL;
354 m_Compass = NULL;
355 m_NotificationsList = NULL;
356 m_notification_button = NULL;
357
358 g_ChartNotRenderScaleFactor = 2.0;
359 m_bShowScaleInStatusBar = true;
360
361 m_muiBar = NULL;
362 m_bShowScaleInStatusBar = false;
363 m_show_focus_bar = true;
364
365 m_bShowOutlines = false;
366 m_bDisplayGrid = false;
367 m_bShowDepthUnits = true;
368 m_encDisplayCategory = (int)STANDARD;
369
370 m_encShowLights = true;
371 m_encShowAnchor = true;
372 m_encShowDataQual = false;
373 m_bShowGPS = true;
374 m_pQuilt = new Quilt(this);
375 SetQuiltMode(true);
376 SetAlertString("");
377 m_sector_glat = 0;
378 m_sector_glon = 0;
379 g_PrintingInProgress = false;
380
381#ifdef HAVE_WX_GESTURE_EVENTS
382 m_oldVPSScale = -1.0;
383 m_popupWanted = false;
384 m_leftdown = false;
385#endif /* HAVE_WX_GESTURE_EVENTS */
386
387 SetupGlCanvas();
388
389 singleClickEventIsValid = false;
390
391 // Build the cursors
392
393 pCursorLeft = NULL;
394 pCursorRight = NULL;
395 pCursorUp = NULL;
396 pCursorDown = NULL;
397 pCursorArrow = NULL;
398 pCursorPencil = NULL;
399 pCursorCross = NULL;
400
401 RebuildCursors();
402
403 SetCursor(*pCursorArrow);
404
405 pPanTimer = new wxTimer(this, m_MouseDragging);
406 pPanTimer->Stop();
407
408 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
409 pMovementTimer->Stop();
410
411 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
412 pMovementStopTimer->Stop();
413
414 pRotDefTimer = new wxTimer(this, ROT_TIMER);
415 pRotDefTimer->Stop();
416
417 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
418 m_DoubleClickTimer->Stop();
419
420 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
421 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
422 m_chart_drag_inertia_active = false;
423
424 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
425 m_animationActive = false;
426
427 m_panx = m_pany = 0;
428 m_panspeed = 0;
429 m_panx_target_final = m_pany_target_final = 0;
430 m_panx_target_now = m_pany_target_now = 0;
431
432 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
433 pCurTrackTimer->Stop();
434 m_curtrack_timer_msec = 10;
435
436 m_wheelzoom_stop_oneshot = 0;
437 m_last_wheel_dir = 0;
438
439 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
440
441 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
442
443 m_rollover_popup_timer_msec = 20;
444
445 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
446
447 m_b_rot_hidef = true;
448
449 proute_bm = NULL;
450 m_prot_bm = NULL;
451
452 m_upMode = NORTH_UP_MODE;
453 m_bLookAhead = false;
454
455 // Set some benign initial values
456
457 m_cs = GLOBAL_COLOR_SCHEME_DAY;
458 VPoint.clat = 0;
459 VPoint.clon = 0;
460 VPoint.view_scale_ppm = 1;
461 VPoint.Invalidate();
462 m_nMeasureState = 0;
463
464 m_canvas_scale_factor = 1.;
465
466 m_canvas_width = 1000;
467
468 m_overzoomTextWidth = 0;
469 m_overzoomTextHeight = 0;
470
471 // Create the default world chart
472 pWorldBackgroundChart = new GSHHSChart;
473 gShapeBasemap.Reset();
474
475 // Create the default depth unit emboss maps
476 m_pEM_Feet = NULL;
477 m_pEM_Meters = NULL;
478 m_pEM_Fathoms = NULL;
479
480 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
481
482 m_pEM_OverZoom = NULL;
483 SetOverzoomFont();
484 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
485
486 // Build icons for tide/current points
487 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
488 m_bmTideDay =
489 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
490
491 // Dusk
492 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
493
494 // Night
495 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
496
497 // Build Dusk/Night ownship icons
498 double factor_dusk = 0.5;
499 double factor_night = 0.25;
500
501 // Red
502 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
503
504 int rimg_width = m_os_image_red_day.GetWidth();
505 int rimg_height = m_os_image_red_day.GetHeight();
506
507 m_os_image_red_dusk = m_os_image_red_day.Copy();
508 m_os_image_red_night = m_os_image_red_day.Copy();
509
510 for (int iy = 0; iy < rimg_height; iy++) {
511 for (int ix = 0; ix < rimg_width; ix++) {
512 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
513 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
514 m_os_image_red_day.GetGreen(ix, iy),
515 m_os_image_red_day.GetBlue(ix, iy));
516 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
517 hsv.value = hsv.value * factor_dusk;
518 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
519 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
520
521 hsv = wxImage::RGBtoHSV(rgb);
522 hsv.value = hsv.value * factor_night;
523 nrgb = wxImage::HSVtoRGB(hsv);
524 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
525 }
526 }
527 }
528
529 // Grey
530 m_os_image_grey_day =
531 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
532
533 int gimg_width = m_os_image_grey_day.GetWidth();
534 int gimg_height = m_os_image_grey_day.GetHeight();
535
536 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
537 m_os_image_grey_night = m_os_image_grey_day.Copy();
538
539 for (int iy = 0; iy < gimg_height; iy++) {
540 for (int ix = 0; ix < gimg_width; ix++) {
541 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
542 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
543 m_os_image_grey_day.GetGreen(ix, iy),
544 m_os_image_grey_day.GetBlue(ix, iy));
545 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
546 hsv.value = hsv.value * factor_dusk;
547 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
548 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
549
550 hsv = wxImage::RGBtoHSV(rgb);
551 hsv.value = hsv.value * factor_night;
552 nrgb = wxImage::HSVtoRGB(hsv);
553 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
554 }
555 }
556 }
557
558 // Yellow
559 m_os_image_yellow_day = m_os_image_red_day.Copy();
560
561 gimg_width = m_os_image_yellow_day.GetWidth();
562 gimg_height = m_os_image_yellow_day.GetHeight();
563
564 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
565 m_os_image_yellow_night = m_os_image_red_day.Copy();
566
567 for (int iy = 0; iy < gimg_height; iy++) {
568 for (int ix = 0; ix < gimg_width; ix++) {
569 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
570 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
571 m_os_image_yellow_day.GetGreen(ix, iy),
572 m_os_image_yellow_day.GetBlue(ix, iy));
573 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
574 hsv.hue += 60. / 360.; // shift to yellow
575 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
576 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
577
578 hsv = wxImage::RGBtoHSV(rgb);
579 hsv.value = hsv.value * factor_dusk;
580 hsv.hue += 60. / 360.; // shift to yellow
581 nrgb = wxImage::HSVtoRGB(hsv);
582 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
583
584 hsv = wxImage::RGBtoHSV(rgb);
585 hsv.hue += 60. / 360.; // shift to yellow
586 hsv.value = hsv.value * factor_night;
587 nrgb = wxImage::HSVtoRGB(hsv);
588 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
589 }
590 }
591 }
592
593 // Set initial pointers to ownship images
594 m_pos_image_red = &m_os_image_red_day;
595 m_pos_image_yellow = &m_os_image_yellow_day;
596 m_pos_image_grey = &m_os_image_grey_day;
597
598 SetUserOwnship();
599
600 m_pBrightPopup = NULL;
601
602#ifdef ocpnUSE_GL
603 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
604#endif
605
606 SetupGridFont();
607
608 m_Piano = new Piano(this);
609
610 m_bShowCompassWin = true;
611 m_Compass = new ocpnCompass(this);
612 m_Compass->SetScaleFactor(g_compass_scalefactor);
613 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
614
615 m_notification_button = new NotificationButton(this);
616 m_notification_button->SetScaleFactor(g_compass_scalefactor);
617 m_notification_button->Show(true);
618
619 m_pianoFrozen = false;
620
621 SetMinSize(wxSize(200, 200));
622
623 m_displayScale = 1.0;
624#if defined(__WXOSX__) || defined(__WXGTK3__)
625 // Support scaled HDPI displays.
626 m_displayScale = GetContentScaleFactor();
627#endif
628 VPoint.SetPixelScale(m_displayScale);
629
630#ifdef HAVE_WX_GESTURE_EVENTS
631 // if (!m_glcc)
632 {
633 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
634 wxLogError("Failed to enable touch events");
635 }
636
637 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
638
639 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
640 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
641
642 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
643 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
644
645 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
646 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
647
648 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
649 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
650 }
651#endif
652
653 // Listen for notification events
654 auto &noteman = NotificationManager::GetInstance();
655
656 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
657 evt_notificationlist_change_listener.Listen(
658 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
659 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
660 if (m_NotificationsList && m_NotificationsList->IsShown()) {
661 m_NotificationsList->ReloadNotificationList();
662 }
663 Refresh();
664 });
665}
666
667ChartCanvas::~ChartCanvas() {
668 delete pThumbDIBShow;
669
670 // Delete Cursors
671 delete pCursorLeft;
672 delete pCursorRight;
673 delete pCursorUp;
674 delete pCursorDown;
675 delete pCursorArrow;
676 delete pCursorPencil;
677 delete pCursorCross;
678
679 delete pPanTimer;
680 delete pMovementTimer;
681 delete pMovementStopTimer;
682 delete pCurTrackTimer;
683 delete pRotDefTimer;
684 delete m_DoubleClickTimer;
685
686 delete m_pTrackRolloverWin;
687 delete m_pRouteRolloverWin;
688 delete m_pAISRolloverWin;
689 delete m_pBrightPopup;
690
691 delete m_pCIWin;
692
693 delete pscratch_bm;
694
695 m_dc_route.SelectObject(wxNullBitmap);
696 delete proute_bm;
697
698 delete pWorldBackgroundChart;
699 delete pss_overlay_bmp;
700
701 delete m_pEM_Feet;
702 delete m_pEM_Meters;
703 delete m_pEM_Fathoms;
704
705 delete m_pEM_OverZoom;
706 // delete m_pEM_CM93Offset;
707
708 delete m_prot_bm;
709
710 delete m_pos_image_user_day;
711 delete m_pos_image_user_dusk;
712 delete m_pos_image_user_night;
713 delete m_pos_image_user_grey_day;
714 delete m_pos_image_user_grey_dusk;
715 delete m_pos_image_user_grey_night;
716 delete m_pos_image_user_yellow_day;
717 delete m_pos_image_user_yellow_dusk;
718 delete m_pos_image_user_yellow_night;
719
720 delete undo;
721#ifdef ocpnUSE_GL
722 if (!g_bdisable_opengl) {
723 delete m_glcc;
724
725#if wxCHECK_VERSION(2, 9, 0)
726 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
727#endif
728 }
729#endif
730
731 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
732 // wx tries to deliver events to this canvas during destroy.
733 MUIBar *muiBar = m_muiBar;
734 m_muiBar = 0;
735 delete muiBar;
736 delete m_pQuilt;
737 delete m_pCurrentStack;
738 delete m_Compass;
739 delete m_Piano;
740}
741
742void ChartCanvas::SetupGridFont() {
743 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
744 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
745 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
746 m_pgridFont = FontMgr::Get().FindOrCreateFont(
747 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
748 FALSE, wxString("Arial"));
749}
750
751void ChartCanvas::RebuildCursors() {
752 delete pCursorLeft;
753 delete pCursorRight;
754 delete pCursorUp;
755 delete pCursorDown;
756 delete pCursorArrow;
757 delete pCursorPencil;
758 delete pCursorCross;
759
760 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
761 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
762
763 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
764
765 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
766 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
767 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
768 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
769 wxImage ICursorPencil =
770 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
771 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
772
773#if !defined(__WXMSW__) && !defined(__WXQT__)
774 ICursorLeft.ConvertAlphaToMask(128);
775 ICursorRight.ConvertAlphaToMask(128);
776 ICursorUp.ConvertAlphaToMask(128);
777 ICursorDown.ConvertAlphaToMask(128);
778 ICursorPencil.ConvertAlphaToMask(10);
779 ICursorCross.ConvertAlphaToMask(10);
780#endif
781
782 if (ICursorLeft.Ok()) {
783 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
784 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
785 pCursorLeft = new wxCursor(ICursorLeft);
786 } else
787 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
788
789 if (ICursorRight.Ok()) {
790 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
791 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
792 pCursorRight = new wxCursor(ICursorRight);
793 } else
794 pCursorRight = new wxCursor(wxCURSOR_ARROW);
795
796 if (ICursorUp.Ok()) {
797 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
798 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
799 pCursorUp = new wxCursor(ICursorUp);
800 } else
801 pCursorUp = new wxCursor(wxCURSOR_ARROW);
802
803 if (ICursorDown.Ok()) {
804 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
805 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
806 pCursorDown = new wxCursor(ICursorDown);
807 } else
808 pCursorDown = new wxCursor(wxCURSOR_ARROW);
809
810 if (ICursorPencil.Ok()) {
811 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
812 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
813 pCursorPencil = new wxCursor(ICursorPencil);
814 } else
815 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
816
817 if (ICursorCross.Ok()) {
818 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
819 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
820 pCursorCross = new wxCursor(ICursorCross);
821 } else
822 pCursorCross = new wxCursor(wxCURSOR_ARROW);
823
824 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
825 pPlugIn_Cursor = NULL;
826}
827
828void ChartCanvas::CanvasApplyLocale() {
829 CreateDepthUnitEmbossMaps(m_cs);
830 CreateOZEmbossMapData(m_cs);
831}
832
833void ChartCanvas::SetupGlCanvas() {
834#ifndef __ANDROID__
835#ifdef ocpnUSE_GL
836 if (!g_bdisable_opengl) {
837 if (g_bopengl) {
838 wxLogMessage("Creating glChartCanvas");
839 m_glcc = new glChartCanvas(this);
840
841 // We use one context for all GL windows, so that textures etc will be
842 // automatically shared
843 if (IsPrimaryCanvas()) {
844 // qDebug() << "Creating Primary Context";
845
846 // wxGLContextAttrs ctxAttr;
847 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
848 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
849 // NULL, &ctxAttr);
850 wxGLContext *pctx = new wxGLContext(m_glcc);
851 m_glcc->SetContext(pctx);
852 g_pGLcontext = pctx; // Save a copy of the common context
853 } else {
854#ifdef __WXOSX__
855 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
856#else
857 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
858 // saved common context
859#endif
860 }
861 }
862 }
863#endif
864#endif
865
866#ifdef __ANDROID__ // ocpnUSE_GL
867 if (!g_bdisable_opengl) {
868 if (g_bopengl) {
869 // qDebug() << "SetupGlCanvas";
870 wxLogMessage("Creating glChartCanvas");
871
872 // We use one context for all GL windows, so that textures etc will be
873 // automatically shared
874 if (IsPrimaryCanvas()) {
875 qDebug() << "Creating Primary glChartCanvas";
876
877 // wxGLContextAttrs ctxAttr;
878 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
879 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
880 // NULL, &ctxAttr);
881 m_glcc = new glChartCanvas(this);
882
883 wxGLContext *pctx = new wxGLContext(m_glcc);
884 m_glcc->SetContext(pctx);
885 g_pGLcontext = pctx; // Save a copy of the common context
886 m_glcc->m_pParentCanvas = this;
887 // m_glcc->Reparent(this);
888 } else {
889 qDebug() << "Creating Secondary glChartCanvas";
890 // QGLContext *pctx =
891 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
892 // << "pctx: " << pctx;
893
894 m_glcc = new glChartCanvas(
895 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
896 // m_glcc = new glChartCanvas(this, pctx); //Shared
897 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
898 wxGLContext *pwxctx = new wxGLContext(m_glcc);
899 m_glcc->SetContext(pwxctx);
900 m_glcc->m_pParentCanvas = this;
901 // m_glcc->Reparent(this);
902 }
903 }
904 }
905#endif
906}
907
908void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
909 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
910
911 // On Android, we get a KillFocus on just about every keystroke.
912 // Why?
913#ifdef __ANDROID__
914 return;
915#endif
916
917 // Special logic:
918 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
919 // canvas focus. Why??? Who knows... So, we provide for this case by
920 // starting a timer if required to actually Finish() a route on a legitimate
921 // focus change, but not if the focus is quickly regained ( <20 msec.) on
922 // this canvas.
923#ifdef __WXOSX__
924 if (m_routeState && m_FinishRouteOnKillFocus)
925 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
926#else
927 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
928#endif
929}
930
931void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
932 m_routeFinishTimer.Stop();
933
934 // Try to keep the global top-line menubar selections up to date with the
935 // current "focus" canvas
936 gFrame->UpdateGlobalMenuItems(this);
937
938 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
939}
940
941void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
942 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
943}
944
945#ifdef HAVE_WX_GESTURE_EVENTS
946void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
947 /* we defer the popup menu call upon the leftup event
948 else the menu disappears immediately,
949 (see
950 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
951 */
952 m_popupWanted = true;
953}
954
955void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
956 // not implemented yet
957}
958
959void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
960
961void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
962
963void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
964 wxPoint pos = event.GetPosition();
965
966 m_leftdown = false;
967
968 if (!m_popupWanted) {
969 wxMouseEvent ev(wxEVT_LEFT_UP);
970 ev.m_x = pos.x;
971 ev.m_y = pos.y;
972 MouseEvent(ev);
973 return;
974 }
975
976 m_popupWanted = false;
977
978 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
979 ev.m_x = pos.x;
980 ev.m_y = pos.y;
981
982 MouseEvent(ev);
983}
984
985void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
986 m_leftdown = true;
987
988 wxPoint pos = event.GetPosition();
989 MouseEvent(event);
990}
991
992void ChartCanvas::OnMotion(wxMouseEvent &event) {
993 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
994 dragging, upon simple click, and without the OnLeftDown event before Thus,
995 this consists in skiping it, and setting the leftdown bit according to a
996 status that we trust */
997 event.m_leftDown = m_leftdown;
998 MouseEvent(event);
999}
1000
1001void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1002 /* there are spurious end zoom events upon right-click */
1003 if (event.IsGestureEnd()) return;
1004
1005 double factor = event.GetZoomFactor();
1006
1007 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1008 m_oldVPSScale = GetVPScale();
1009 }
1010
1011 double current_vps = GetVPScale();
1012 double wanted_factor = m_oldVPSScale / current_vps * factor;
1013
1014 ZoomCanvas(wanted_factor, true, false);
1015
1016 // Allow combined zoom/pan operation
1017 if (event.IsGestureStart()) {
1018 m_zoomStartPoint = event.GetPosition();
1019 } else {
1020 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1021 PanCanvas(-delta.x, -delta.y);
1022 m_zoomStartPoint = event.GetPosition();
1023 }
1024}
1025
1026void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1027
1028void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1029 DoRotateCanvas(0.0);
1030}
1031#endif /* HAVE_WX_GESTURE_EVENTS */
1032
1033void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1034 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1035 m_vLat = pcc->iLat;
1036 m_vLon = pcc->iLon;
1037
1038 m_restore_dbindex = pcc->DBindex;
1039 m_bFollow = pcc->bFollow;
1040 if (pcc->GroupID < 0) pcc->GroupID = 0;
1041
1042 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1043 m_groupIndex = 0;
1044 else
1045 m_groupIndex = pcc->GroupID;
1046
1047 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1048
1049 ShowTides(pcc->bShowTides);
1050 ShowCurrents(pcc->bShowCurrents);
1051
1052 SetShowDepthUnits(pcc->bShowDepthUnits);
1053 SetShowGrid(pcc->bShowGrid);
1054 SetShowOutlines(pcc->bShowOutlines);
1055
1056 SetShowAIS(pcc->bShowAIS);
1057 SetAttenAIS(pcc->bAttenAIS);
1058
1059 // ENC options
1060 SetShowENCText(pcc->bShowENCText);
1061 m_encDisplayCategory = pcc->nENCDisplayCategory;
1062 m_encShowDepth = pcc->bShowENCDepths;
1063 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1064 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1065 m_encShowLights = pcc->bShowENCLights;
1066 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1067 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1068 m_encShowDataQual = pcc->bShowENCDataQuality;
1069
1070 bool courseUp = pcc->bCourseUp;
1071 bool headUp = pcc->bHeadUp;
1072 m_upMode = NORTH_UP_MODE;
1073 if (courseUp)
1074 m_upMode = COURSE_UP_MODE;
1075 else if (headUp)
1076 m_upMode = HEAD_UP_MODE;
1077
1078 m_bLookAhead = pcc->bLookahead;
1079
1080 m_singleChart = NULL;
1081}
1082
1083void ChartCanvas::ApplyGlobalSettings() {
1084 // GPS compas window
1085 if (m_Compass) {
1086 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1087 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1088 }
1089 m_notification_button->UpdateStatus();
1090}
1091
1092void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1093 bool groupOK = CheckGroup(m_groupIndex);
1094
1095 if (!groupOK) {
1096 SetGroupIndex(m_groupIndex, true);
1097 }
1098}
1099
1100void ChartCanvas::SetShowGPS(bool bshow) {
1101 if (m_bShowGPS != bshow) {
1102 delete m_Compass;
1103 m_Compass = new ocpnCompass(this, bshow);
1104 m_Compass->SetScaleFactor(g_compass_scalefactor);
1105 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1106 }
1107 m_bShowGPS = bshow;
1108}
1109
1110void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1111 m_bShowCompassWin = bshow;
1112 if (m_Compass) {
1113 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1114 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1115 }
1116}
1117
1118int ChartCanvas::GetPianoHeight() {
1119 int height = 0;
1120 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1121
1122 return height;
1123}
1124
1125void ChartCanvas::ConfigureChartBar() {
1126 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1127
1128 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1129 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1130
1131 if (GetQuiltMode()) {
1132 m_Piano->SetRoundedRectangles(true);
1133 }
1134 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1135 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1136 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1137}
1138
1139void ChartCanvas::ShowTides(bool bShow) {
1140 gFrame->LoadHarmonics();
1141
1142 if (ptcmgr->IsReady()) {
1143 SetbShowTide(bShow);
1144
1145 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1146 } else {
1147 wxLogMessage("Chart1::Event...TCMgr Not Available");
1148 SetbShowTide(false);
1149 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1150 }
1151
1152 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1153 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1154
1155 // TODO
1156 // if( GetbShowTide() ) {
1157 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1158 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1159 // update
1160 // } else
1161 // FrameTCTimer.Stop();
1162}
1163
1164void ChartCanvas::ShowCurrents(bool bShow) {
1165 gFrame->LoadHarmonics();
1166
1167 if (ptcmgr->IsReady()) {
1168 SetbShowCurrent(bShow);
1169 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1170 } else {
1171 wxLogMessage("Chart1::Event...TCMgr Not Available");
1172 SetbShowCurrent(false);
1173 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1174 }
1175
1176 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1177 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1178
1179 // TODO
1180 // if( GetbShowCurrent() ) {
1181 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1182 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1183 // update
1184 // } else
1185 // FrameTCTimer.Stop();
1186}
1187
1188// TODO
1189static ChartDummy *pDummyChart;
1190
1193
1194void ChartCanvas::canvasRefreshGroupIndex(void) { SetGroupIndex(m_groupIndex); }
1195
1196void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1197 SetAlertString("");
1198
1199 int new_index = index;
1200 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1201
1202 bool bgroup_override = false;
1203 int old_group_index = new_index;
1204
1205 if (!CheckGroup(new_index)) {
1206 new_index = 0;
1207 bgroup_override = true;
1208 }
1209
1210 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1211 new_index = index;
1212
1213 // Get the currently displayed chart native scale, and the current ViewPort
1214 int current_chart_native_scale = GetCanvasChartNativeScale();
1215 ViewPort vp = GetVP();
1216
1217 m_groupIndex = new_index;
1218
1219 // Are there ENCs in this group
1220 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1221
1222 // Update the MUIBar for ENC availability
1223 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1224
1225 // Allow the chart database to pre-calculate the MBTile inclusion test
1226 // boolean...
1227 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1228
1229 // Invalidate the "sticky" chart on group change, since it might not be in
1230 // the new group
1231 g_sticky_chart = -1;
1232
1233 // We need a chartstack and quilt to figure out which chart to open in the
1234 // new group
1235 UpdateCanvasOnGroupChange();
1236
1237 int dbi_now = -1;
1238 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1239
1240 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1241
1242 // If a new reference chart is indicated, set a good scale for it.
1243 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1244 double best_scale = GetBestStartScale(dbi_hint, vp);
1245 SetVPScale(best_scale);
1246 }
1247
1248 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1249
1250 // Refresh the canvas, selecting the "best" chart,
1251 // applying the prior ViewPort exactly
1252 canvasChartsRefresh(dbi_hint);
1253
1254 UpdateCanvasControlBar();
1255
1256 if (!autoSwitch && bgroup_override) {
1257 // show a short timed message box
1258 wxString msg(_("Group \""));
1259
1260 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1261 msg += pGroup->m_group_name;
1262
1263 msg += _("\" is empty.");
1264
1265 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1266
1267 return;
1268 }
1269
1270 // Message box is deferred so that canvas refresh occurs properly before
1271 // dialog
1272 if (bgroup_override) {
1273 wxString msg(_("Group \""));
1274
1275 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1276 msg += pGroup->m_group_name;
1277
1278 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1279
1280 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1281 }
1282}
1283
1284bool ChartCanvas::CheckGroup(int igroup) {
1285 if (!ChartData) return true; // Not known yet...
1286
1287 if (igroup == 0) return true; // "all charts" is always OK
1288
1289 if (igroup < 0) // negative group is an error
1290 return false;
1291
1292 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1293
1294 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1295 // and auto-shift to group 0
1296 return false;
1297
1298 for (const auto &elem : pGroup->m_element_array) {
1299 for (unsigned int ic = 0;
1300 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1301 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1302 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1303
1304 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1305 }
1306 }
1307
1308 // If necessary, check for GSHHS
1309 for (const auto &elem : pGroup->m_element_array) {
1310 const wxString &element_root = elem.m_element_name;
1311 wxString test_string = "GSHH";
1312 if (element_root.Upper().Contains(test_string)) return true;
1313 }
1314
1315 return false;
1316}
1317
1318void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1319 if (!ChartData) return;
1320
1321 AbstractPlatform::ShowBusySpinner();
1322
1323 double old_scale = GetVPScale();
1324 InvalidateQuilt();
1325 SetQuiltRefChart(-1);
1326
1327 m_singleChart = NULL;
1328
1329 // delete m_pCurrentStack;
1330 // m_pCurrentStack = NULL;
1331
1332 // Build a new ChartStack
1333 if (!m_pCurrentStack) {
1334 m_pCurrentStack = new ChartStack;
1335 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1336 }
1337
1338 if (-1 != dbi_hint) {
1339 if (GetQuiltMode()) {
1340 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1341 SetQuiltRefChart(dbi_hint);
1342 } else {
1343 // Open the saved chart
1344 ChartBase *pTentative_Chart;
1345 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1346
1347 if (pTentative_Chart) {
1348 /* m_singleChart is always NULL here, (set above) should this go before
1349 * that? */
1350 if (m_singleChart) m_singleChart->Deactivate();
1351
1352 m_singleChart = pTentative_Chart;
1353 m_singleChart->Activate();
1354
1355 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1356 GetpCurrentStack(), m_singleChart->GetFullPath());
1357 }
1358 }
1359
1360 // refresh_Piano();
1361 } else {
1362 // Select reference chart from the stack, as though clicked by user
1363 // Make it the smallest scale chart on the stack
1364 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1365 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1366 SetQuiltRefChart(selected_index);
1367 }
1368
1369 // Validate the correct single chart, or set the quilt mode as appropriate
1370 SetupCanvasQuiltMode();
1371 if (!GetQuiltMode() && m_singleChart == 0) {
1372 // use a dummy like in DoChartUpdate
1373 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1374 m_singleChart = pDummyChart;
1375 SetVPScale(old_scale);
1376 }
1377
1378 ReloadVP();
1379
1380 UpdateCanvasControlBar();
1381 UpdateGPSCompassStatusBox(true);
1382
1383 SetCursor(wxCURSOR_ARROW);
1384
1385 AbstractPlatform::HideBusySpinner();
1386}
1387
1388bool ChartCanvas::DoCanvasUpdate(void) {
1389 double tLat, tLon; // Chart Stack location
1390 double vpLat, vpLon; // ViewPort location
1391 bool blong_jump = false;
1392 meters_to_shift = 0;
1393 dir_to_shift = 0;
1394
1395 bool bNewChart = false;
1396 bool bNewView = false;
1397 bool bCanvasChartAutoOpen = true; // debugging
1398
1399 bool bNewPiano = false;
1400 bool bOpenSpecified;
1401 ChartStack LastStack;
1402 ChartBase *pLast_Ch;
1403
1404 ChartStack WorkStack;
1405
1406 if (bDBUpdateInProgress) return false;
1407 if (!ChartData) return false;
1408
1409 if (ChartData->IsBusy()) return false;
1410 if (m_chart_drag_inertia_active) return false;
1411
1412 // Startup case:
1413 // Quilting is enabled, but the last chart seen was not quiltable
1414 // In this case, drop to single chart mode, set persistence flag,
1415 // And open the specified chart
1416 // TODO implement this
1417 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1418 // if( GetQuiltMode() ) {
1419 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1420 // gFrame->ToggleQuiltMode();
1421 // m_bpersistent_quilt = true;
1422 // m_singleChart = NULL;
1423 // }
1424 // }
1425 // }
1426
1427 // If in auto-follow mode, use the current glat,glon to build chart
1428 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1429 // other means
1430
1431 if (m_bFollow) {
1432 tLat = gLat;
1433 tLon = gLon;
1434
1435 // Set the ViewPort center based on the OWNSHIP offset
1436 double dx = m_OSoffsetx;
1437 double dy = m_OSoffsety;
1438 double d_east = dx / GetVP().view_scale_ppm;
1439 double d_north = dy / GetVP().view_scale_ppm;
1440
1441 if (GetUpMode() == NORTH_UP_MODE) {
1442 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1443 } else {
1444 double offset_angle = atan2(d_north, d_east);
1445 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1446 double chart_angle = GetVPRotation();
1447 double target_angle = chart_angle + offset_angle;
1448 double d_east_mod = offset_distance * cos(target_angle);
1449 double d_north_mod = offset_distance * sin(target_angle);
1450 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1451 }
1452
1453 // on lookahead mode, adjust the vp center point
1454 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1455 double cog_to_use = gCog;
1456 if (g_btenhertz &&
1457 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1458 cog_to_use = gCog_gt;
1459 blong_jump = true;
1460 }
1461 if (!g_btenhertz) cog_to_use = g_COGAvg;
1462
1463 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1464
1465 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1466 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1467
1468 double pixel_delta_tent =
1469 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1470
1471 double pixel_delta = 0;
1472
1473 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1474 // avoid jumping of the vp center point during slow maneuvering, or at
1475 // anchor....
1476 if (!std::isnan(gSog)) {
1477 if (gSog < 2.0)
1478 pixel_delta = 0.;
1479 else
1480 pixel_delta = pixel_delta_tent;
1481 }
1482
1483 meters_to_shift = 0;
1484 dir_to_shift = 0;
1485 if (!std::isnan(gCog)) {
1486 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1487 dir_to_shift = cog_to_use;
1488 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1489 &vpLon);
1490 } else {
1491 vpLat = gLat;
1492 vpLon = gLon;
1493 }
1494 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1495 m_OSoffsetx = 0; // center ownship on loss of GPS
1496 m_OSoffsety = 0;
1497 vpLat = gLat;
1498 vpLon = gLon;
1499 }
1500
1501 } else {
1502 tLat = m_vLat;
1503 tLon = m_vLon;
1504 vpLat = m_vLat;
1505 vpLon = m_vLon;
1506 }
1507
1508 if (GetQuiltMode()) {
1509 int current_db_index = -1;
1510 if (m_pCurrentStack)
1511 current_db_index =
1512 m_pCurrentStack
1513 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1514 // chart dbIndex
1515 else
1516 m_pCurrentStack = new ChartStack;
1517
1518 // This logic added to enable opening a chart when there is no
1519 // previous chart indication, either from inital startup, or from adding
1520 // new chart directory
1521 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1522 m_pCurrentStack) {
1523 if (m_pCurrentStack->nEntry) {
1524 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1525 1); // smallest scale
1526 SelectQuiltRefdbChart(new_dbIndex, true);
1527 m_bautofind = false;
1528 }
1529 }
1530
1531 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1532 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1533
1534 if (m_bFirstAuto) {
1535 // Allow the chart database to pre-calculate the MBTile inclusion test
1536 // boolean...
1537 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1538
1539 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1540 // physical pixels. On standard DPI displays where logical = physical
1541 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1542 // logical pixels, this ratio would be 0.5.
1543 double proposed_scale_onscreen =
1544 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1545
1546 int initial_db_index = m_restore_dbindex;
1547 if (initial_db_index < 0) {
1548 if (m_pCurrentStack->nEntry) {
1549 initial_db_index =
1550 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1551 } else
1552 m_bautofind = true; // initial_db_index = 0;
1553 }
1554
1555 if (m_pCurrentStack->nEntry) {
1556 int initial_type = ChartData->GetDBChartType(initial_db_index);
1557
1558 // Check to see if the target new chart is quiltable as a reference
1559 // chart
1560
1561 if (!IsChartQuiltableRef(initial_db_index)) {
1562 // If it is not quiltable, then walk the stack up looking for a
1563 // satisfactory chart i.e. one that is quiltable and of the same type
1564 // XXX if there's none?
1565 int stack_index = 0;
1566
1567 if (stack_index >= 0) {
1568 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1569 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1570 if (IsChartQuiltableRef(test_db_index) &&
1571 (initial_type ==
1572 ChartData->GetDBChartType(initial_db_index))) {
1573 initial_db_index = test_db_index;
1574 break;
1575 }
1576 stack_index++;
1577 }
1578 }
1579 }
1580
1581 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1582 if (pc) {
1583 SetQuiltRefChart(initial_db_index);
1584 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1585 }
1586 }
1587 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1588 // just GetVPScale(), so I'm not sure why it's necessary to define the
1589 // proposed_scale_onscreen variable.
1590 bNewView |= SetViewPoint(vpLat, vpLon,
1591 GetCanvasScaleFactor() / proposed_scale_onscreen,
1592 0, GetVPRotation());
1593 }
1594 // Measure rough jump distance if in bfollow mode
1595 // No good reason to do smooth pan for
1596 // jump distance more than one screen width at scale.
1597 bool super_jump = false;
1598 if (m_bFollow) {
1599 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1600 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1601 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1602 }
1603#if 0
1604 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead && !g_btouch && !m_bzooming) {
1605 int nstep = 5;
1606 if (blong_jump) nstep = 20;
1607 StartTimedMovementVP(vpLat, vpLon, nstep);
1608 } else
1609#endif
1610 {
1611 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1612 }
1613
1614 goto update_finish;
1615 }
1616
1617 // Single Chart Mode from here....
1618 pLast_Ch = m_singleChart;
1619 ChartTypeEnum new_open_type;
1620 ChartFamilyEnum new_open_family;
1621 if (pLast_Ch) {
1622 new_open_type = pLast_Ch->GetChartType();
1623 new_open_family = pLast_Ch->GetChartFamily();
1624 } else {
1625 new_open_type = CHART_TYPE_KAP;
1626 new_open_family = CHART_FAMILY_RASTER;
1627 }
1628
1629 bOpenSpecified = m_bFirstAuto;
1630
1631 // Make sure the target stack is valid
1632 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1633
1634 // Build a chart stack based on tLat, tLon
1635 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1636 m_groupIndex)) { // Bogus Lat, Lon?
1637 if (NULL == pDummyChart) {
1638 pDummyChart = new ChartDummy;
1639 bNewChart = true;
1640 }
1641
1642 if (m_singleChart)
1643 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1644
1645 m_singleChart = pDummyChart;
1646
1647 // If the current viewpoint is invalid, set the default scale to
1648 // something reasonable.
1649 double set_scale = GetVPScale();
1650 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1651
1652 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1653
1654 // If the chart stack has just changed, there is new status
1655 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1656 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1657 bNewPiano = true;
1658 bNewChart = true;
1659 }
1660 }
1661
1662 // Copy the new (by definition empty) stack into the target stack
1663 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1664
1665 goto update_finish;
1666 }
1667
1668 // Check to see if Chart Stack has changed
1669 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1670 // New chart stack, so...
1671 bNewPiano = true;
1672
1673 // Save a copy of the current stack
1674 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1675
1676 // Copy the new stack into the target stack
1677 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1678
1679 // Is Current Chart in new stack?
1680
1681 int tEntry = -1;
1682 if (NULL != m_singleChart) // this handles startup case
1683 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1684 m_singleChart->GetFullPath());
1685
1686 if (tEntry != -1) { // m_singleChart is in the new stack
1687 m_pCurrentStack->CurrentStackEntry = tEntry;
1688 bNewChart = false;
1689 }
1690
1691 else // m_singleChart is NOT in new stack
1692 { // So, need to open a new chart
1693 // Find the largest scale raster chart that opens OK
1694
1695 ChartBase *pProposed = NULL;
1696
1697 if (bCanvasChartAutoOpen) {
1698 bool search_direction =
1699 false; // default is to search from lowest to highest
1700 int start_index = 0;
1701
1702 // A special case: If panning at high scale, open largest scale
1703 // chart first
1704 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1705 (LastStack.nEntry == 0)) {
1706 search_direction = true;
1707 start_index = m_pCurrentStack->nEntry - 1;
1708 }
1709
1710 // Another special case, open specified index on program start
1711 if (bOpenSpecified) {
1712 search_direction = false;
1713 start_index = 0;
1714 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1715 start_index = 0;
1716
1717 new_open_type = CHART_TYPE_DONTCARE;
1718 }
1719
1720 pProposed = ChartData->OpenStackChartConditional(
1721 m_pCurrentStack, start_index, search_direction, new_open_type,
1722 new_open_family);
1723
1724 // Try to open other types/families of chart in some priority
1725 if (NULL == pProposed)
1726 pProposed = ChartData->OpenStackChartConditional(
1727 m_pCurrentStack, start_index, search_direction,
1728 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1729
1730 if (NULL == pProposed)
1731 pProposed = ChartData->OpenStackChartConditional(
1732 m_pCurrentStack, start_index, search_direction,
1733 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1734
1735 bNewChart = true;
1736
1737 } // bCanvasChartAutoOpen
1738
1739 else
1740 pProposed = NULL;
1741
1742 // If no go, then
1743 // Open a Dummy Chart
1744 if (NULL == pProposed) {
1745 if (NULL == pDummyChart) {
1746 pDummyChart = new ChartDummy;
1747 bNewChart = true;
1748 }
1749
1750 if (pLast_Ch)
1751 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1752
1753 pProposed = pDummyChart;
1754 }
1755
1756 // Arriving here, pProposed points to an opened chart, or NULL.
1757 if (m_singleChart) m_singleChart->Deactivate();
1758 m_singleChart = pProposed;
1759
1760 if (m_singleChart) {
1761 m_singleChart->Activate();
1762 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1763 m_pCurrentStack, m_singleChart->GetFullPath());
1764 }
1765 } // need new chart
1766
1767 // Arriving here, m_singleChart is opened and OK, or NULL
1768 if (NULL != m_singleChart) {
1769 // Setup the view using the current scale
1770 double set_scale = GetVPScale();
1771
1772 // If the current viewpoint is invalid, set the default scale to
1773 // something reasonable.
1774 if (!GetVP().IsValid())
1775 set_scale = 1. / 20000.;
1776 else { // otherwise, match scale if elected.
1777 double proposed_scale_onscreen;
1778
1779 if (m_bFollow) { // autoset the scale only if in autofollow
1780 double new_scale_ppm =
1781 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1782 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1783 } else
1784 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1785
1786 // This logic will bring a new chart onscreen at roughly twice the true
1787 // paper scale equivalent. Note that first chart opened on application
1788 // startup (bOpenSpecified = true) will open at the config saved scale
1789 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1790 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1791 double equivalent_vp_scale =
1792 GetCanvasScaleFactor() / proposed_scale_onscreen;
1793 double new_scale_ppm =
1794 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1795 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1796 }
1797
1798 if (m_bFollow) { // bounds-check the scale only if in autofollow
1799 proposed_scale_onscreen =
1800 wxMin(proposed_scale_onscreen,
1801 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1802 GetCanvasWidth()));
1803 proposed_scale_onscreen =
1804 wxMax(proposed_scale_onscreen,
1805 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1807 }
1808
1809 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1810 }
1811
1812 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1813 m_singleChart->GetChartSkew() * PI / 180.,
1814 GetVPRotation());
1815 }
1816 } // new stack
1817
1818 else // No change in Chart Stack
1819 {
1820 if ((m_bFollow) && m_singleChart)
1821 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1822 m_singleChart->GetChartSkew() * PI / 180.,
1823 GetVPRotation());
1824 }
1825
1826update_finish:
1827
1828 // TODO
1829 // if( bNewPiano ) UpdateControlBar();
1830
1831 m_bFirstAuto = false; // Auto open on program start
1832
1833 // If we need a Refresh(), do it here...
1834 // But don't duplicate a Refresh() done by SetViewPoint()
1835 if (bNewChart && !bNewView) Refresh(false);
1836
1837#ifdef ocpnUSE_GL
1838 // If a new chart, need to invalidate gl viewport for refresh
1839 // so the fbo gets flushed
1840 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1841#endif
1842
1843 return bNewChart | bNewView;
1844}
1845
1846void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1847 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1848
1849 SetQuiltRefChart(db_index);
1850 if (ChartData) {
1851 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1852 if (pc) {
1853 if (b_autoscale) {
1854 double best_scale_ppm = GetBestVPScale(pc);
1855 SetVPScale(best_scale_ppm);
1856 }
1857 } else
1858 SetQuiltRefChart(-1);
1859 } else
1860 SetQuiltRefChart(-1);
1861}
1862
1863void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1864 std::vector<int> piano_chart_index_array =
1865 GetQuiltExtendedStackdbIndexArray();
1866 int current_db_index = piano_chart_index_array[selected_index];
1867
1868 SelectQuiltRefdbChart(current_db_index);
1869}
1870
1871double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1872 if (pchart) {
1873 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1874
1875 if ((g_bPreserveScaleOnX) ||
1876 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
1877 double new_scale_ppm = GetVPScale();
1878 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1879 } else {
1880 // This logic will bring the new chart onscreen at roughly twice the true
1881 // paper scale equivalent.
1882 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
1883 double equivalent_vp_scale =
1884 GetCanvasScaleFactor() / proposed_scale_onscreen;
1885 double new_scale_ppm =
1886 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1887 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1888 }
1889
1890 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
1891 // set. Otherwise, we get severe performance problems on all platforms
1892
1893 double max_underzoom_multiplier = 2.0;
1894 if (GetVP().b_quilt) {
1895 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
1896 pchart->GetChartType(),
1897 pchart->GetChartFamily());
1898 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
1899 }
1900
1901 proposed_scale_onscreen = wxMin(
1902 proposed_scale_onscreen,
1903 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
1904 max_underzoom_multiplier);
1905
1906 // And, do not allow excessive overzoom either
1907 proposed_scale_onscreen =
1908 wxMax(proposed_scale_onscreen,
1909 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
1910
1911 return GetCanvasScaleFactor() / proposed_scale_onscreen;
1912 } else
1913 return 1.0;
1914}
1915
1916void ChartCanvas::SetupCanvasQuiltMode(void) {
1917 if (GetQuiltMode()) // going to quilt mode
1918 {
1919 ChartData->LockCache();
1920
1921 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
1922
1923 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1924
1925 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1926 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1927 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1928 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1929
1930 m_Piano->SetRoundedRectangles(true);
1931
1932 // Select the proper Ref chart
1933 int target_new_dbindex = -1;
1934 if (m_pCurrentStack) {
1935 target_new_dbindex =
1936 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
1937
1938 if (-1 != target_new_dbindex) {
1939 if (!IsChartQuiltableRef(target_new_dbindex)) {
1940 int proj = ChartData->GetDBChartProj(target_new_dbindex);
1941 int type = ChartData->GetDBChartType(target_new_dbindex);
1942
1943 // walk the stack up looking for a satisfactory chart
1944 int stack_index = m_pCurrentStack->CurrentStackEntry;
1945
1946 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
1947 (stack_index >= 0)) {
1948 int proj_tent = ChartData->GetDBChartProj(
1949 m_pCurrentStack->GetDBIndex(stack_index));
1950 int type_tent = ChartData->GetDBChartType(
1951 m_pCurrentStack->GetDBIndex(stack_index));
1952
1953 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
1954 if ((proj == proj_tent) && (type_tent == type)) {
1955 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
1956 break;
1957 }
1958 }
1959 stack_index++;
1960 }
1961 }
1962 }
1963 }
1964
1965 if (IsChartQuiltableRef(target_new_dbindex))
1966 SelectQuiltRefdbChart(target_new_dbindex,
1967 false); // Try not to allow a scale change
1968 else
1969 SelectQuiltRefdbChart(-1, false);
1970
1971 m_singleChart = NULL; // Bye....
1972
1973 // Re-qualify the quilt reference chart selection
1974 AdjustQuiltRefChart();
1975
1976 // Restore projection type saved on last quilt mode toggle
1977 // TODO
1978 // if(g_sticky_projection != -1)
1979 // GetVP().SetProjectionType(g_sticky_projection);
1980 // else
1981 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
1982 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
1983
1984 } else // going to SC Mode
1985 {
1986 std::vector<int> empty_array;
1987 m_Piano->SetActiveKeyArray(empty_array);
1988 m_Piano->SetNoshowIndexArray(empty_array);
1989 m_Piano->SetEclipsedIndexArray(empty_array);
1990
1991 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1992 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1993 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1994 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1995 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1996
1997 m_Piano->SetRoundedRectangles(false);
1998 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
1999 }
2000
2001 // When shifting from quilt to single chart mode, select the "best" single
2002 // chart to show
2003 if (!GetQuiltMode()) {
2004 if (ChartData && ChartData->IsValid()) {
2005 UnlockQuilt();
2006
2007 double tLat, tLon;
2008 if (m_bFollow == true) {
2009 tLat = gLat;
2010 tLon = gLon;
2011 } else {
2012 tLat = m_vLat;
2013 tLon = m_vLon;
2014 }
2015
2016 if (!m_singleChart) {
2017 // Build a temporary chart stack based on tLat, tLon
2018 ChartStack TempStack;
2019 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2020 m_groupIndex);
2021
2022 // Iterate over the quilt charts actually shown, looking for the
2023 // largest scale chart that will be in the new chartstack.... This
2024 // will (almost?) always be the reference chart....
2025
2026 ChartBase *Candidate_Chart = NULL;
2027 int cur_max_scale = (int)1e8;
2028
2029 ChartBase *pChart = GetFirstQuiltChart();
2030 while (pChart) {
2031 // Is this pChart in new stack?
2032 int tEntry =
2033 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2034 if (tEntry != -1) {
2035 if (pChart->GetNativeScale() < cur_max_scale) {
2036 Candidate_Chart = pChart;
2037 cur_max_scale = pChart->GetNativeScale();
2038 }
2039 }
2040 pChart = GetNextQuiltChart();
2041 }
2042
2043 m_singleChart = Candidate_Chart;
2044
2045 // If the quilt is empty, there is no "best" chart.
2046 // So, open the smallest scale chart in the current stack
2047 if (NULL == m_singleChart) {
2048 m_singleChart = ChartData->OpenStackChartConditional(
2049 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2050 CHART_FAMILY_DONTCARE);
2051 }
2052 }
2053
2054 // Invalidate all the charts in the quilt,
2055 // as any cached data may be region based and not have fullscreen coverage
2056 InvalidateAllQuiltPatchs();
2057
2058 if (m_singleChart) {
2059 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2060 std::vector<int> one_array;
2061 one_array.push_back(dbi);
2062 m_Piano->SetActiveKeyArray(one_array);
2063 }
2064
2065 if (m_singleChart) {
2066 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2067 }
2068 }
2069 // Invalidate the current stack so that it will be rebuilt on next tick
2070 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2071 }
2072}
2073
2074bool ChartCanvas::IsTempMenuBarEnabled() {
2075#ifdef __WXMSW__
2076 int major;
2077 wxGetOsVersion(&major);
2078 return (major >
2079 5); // For Windows, function is only available on Vista and above
2080#else
2081 return true;
2082#endif
2083}
2084
2085double ChartCanvas::GetCanvasRangeMeters() {
2086 int width, height;
2087 GetSize(&width, &height);
2088 int minDimension = wxMin(width, height);
2089
2090 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2091 range *= cos(GetVP().clat * PI / 180.);
2092 return range;
2093}
2094
2095void ChartCanvas::SetCanvasRangeMeters(double range) {
2096 int width, height;
2097 GetSize(&width, &height);
2098 int minDimension = wxMin(width, height);
2099
2100 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2101 SetVPScale(scale_ppm / 2);
2102}
2103
2104bool ChartCanvas::SetUserOwnship() {
2105 // Look for user defined ownship image
2106 // This may be found in the shared data location along with other user
2107 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2108 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2109 double factor_dusk = 0.5;
2110 double factor_night = 0.25;
2111
2112 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2113 m_pos_image_user_day = new wxImage;
2114 *m_pos_image_user_day = pbmp->ConvertToImage();
2115 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2116
2117 int gimg_width = m_pos_image_user_day->GetWidth();
2118 int gimg_height = m_pos_image_user_day->GetHeight();
2119
2120 // Make dusk and night images
2121 m_pos_image_user_dusk = new wxImage;
2122 m_pos_image_user_night = new wxImage;
2123
2124 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2125 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2126
2127 for (int iy = 0; iy < gimg_height; iy++) {
2128 for (int ix = 0; ix < gimg_width; ix++) {
2129 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2130 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2131 m_pos_image_user_day->GetGreen(ix, iy),
2132 m_pos_image_user_day->GetBlue(ix, iy));
2133 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2134 hsv.value = hsv.value * factor_dusk;
2135 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2136 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2137 nrgb.blue);
2138
2139 hsv = wxImage::RGBtoHSV(rgb);
2140 hsv.value = hsv.value * factor_night;
2141 nrgb = wxImage::HSVtoRGB(hsv);
2142 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2143 nrgb.blue);
2144 }
2145 }
2146 }
2147
2148 // Make some alternate greyed out day/dusk/night images
2149 m_pos_image_user_grey_day = new wxImage;
2150 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2151
2152 m_pos_image_user_grey_dusk = new wxImage;
2153 m_pos_image_user_grey_night = new wxImage;
2154
2155 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2156 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2157
2158 for (int iy = 0; iy < gimg_height; iy++) {
2159 for (int ix = 0; ix < gimg_width; ix++) {
2160 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2161 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2162 m_pos_image_user_grey_day->GetGreen(ix, iy),
2163 m_pos_image_user_grey_day->GetBlue(ix, iy));
2164 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2165 hsv.value = hsv.value * factor_dusk;
2166 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2167 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2168 nrgb.blue);
2169
2170 hsv = wxImage::RGBtoHSV(rgb);
2171 hsv.value = hsv.value * factor_night;
2172 nrgb = wxImage::HSVtoRGB(hsv);
2173 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2174 nrgb.blue);
2175 }
2176 }
2177 }
2178
2179 // Make a yellow image for rendering under low accuracy chart conditions
2180 m_pos_image_user_yellow_day = new wxImage;
2181 m_pos_image_user_yellow_dusk = new wxImage;
2182 m_pos_image_user_yellow_night = new wxImage;
2183
2184 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2185 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2186 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2187
2188 for (int iy = 0; iy < gimg_height; iy++) {
2189 for (int ix = 0; ix < gimg_width; ix++) {
2190 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2191 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2192 m_pos_image_user_grey_day->GetGreen(ix, iy),
2193 m_pos_image_user_grey_day->GetBlue(ix, iy));
2194
2195 // Simply remove all "blue" from the greyscaled image...
2196 // so, what is not black becomes yellow.
2197 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2198 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2199 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2200
2201 hsv = wxImage::RGBtoHSV(rgb);
2202 hsv.value = hsv.value * factor_dusk;
2203 nrgb = wxImage::HSVtoRGB(hsv);
2204 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2205
2206 hsv = wxImage::RGBtoHSV(rgb);
2207 hsv.value = hsv.value * factor_night;
2208 nrgb = wxImage::HSVtoRGB(hsv);
2209 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2210 0);
2211 }
2212 }
2213 }
2214
2215 return true;
2216 } else
2217 return false;
2218}
2219
2221 m_display_size_mm = size;
2222
2223 // int sx, sy;
2224 // wxDisplaySize( &sx, &sy );
2225
2226 // Calculate logical pixels per mm for later reference.
2227 wxSize sd = g_Platform->getDisplaySize();
2228 double horizontal = sd.x;
2229 // Set DPI (Win) scale factor
2230 g_scaler = g_Platform->GetDisplayDIPMult(this);
2231
2232 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2233 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2234
2235 if (ps52plib) {
2236 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2237 ps52plib->SetPPMM(m_pix_per_mm);
2238 }
2239
2240 wxString msg;
2241 msg.Printf(
2242 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2243 "%d:%d ",
2244 m_display_size_mm, sd.x, sd.y);
2245 wxLogMessage(msg);
2246
2247 int ssx, ssy;
2248 ssx = g_monitor_info[g_current_monitor].width;
2249 ssy = g_monitor_info[g_current_monitor].height;
2250 msg.Printf("monitor size: %d %d", ssx, ssy);
2251 wxLogMessage(msg);
2252
2253 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2254}
2255#if 0
2256void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2257{
2258 wxString msg(event.m_string.c_str(), wxConvUTF8);
2259 // if cpus are removed between runs
2260 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2261 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2262 }
2263
2264 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2265 {
2266 compress_msg_array.RemoveAt(event.thread);
2267 compress_msg_array.Insert( msg, event.thread);
2268 }
2269 else
2270 compress_msg_array.Add(msg);
2271
2272
2273 wxString combined_msg;
2274 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2275 combined_msg += compress_msg_array[i];
2276 combined_msg += "\n";
2277 }
2278
2279 bool skip = false;
2280 pprog->Update(pprog_count, combined_msg, &skip );
2281 pprog->SetSize(pprog_size);
2282 if(skip)
2283 b_skipout = skip;
2284}
2285#endif
2286void ChartCanvas::InvalidateGL() {
2287 if (!m_glcc) return;
2288#ifdef ocpnUSE_GL
2289 if (g_bopengl) m_glcc->Invalidate();
2290#endif
2291 if (m_Compass) m_Compass->UpdateStatus(true);
2292}
2293
2294int ChartCanvas::GetCanvasChartNativeScale() {
2295 int ret = 1;
2296 if (!VPoint.b_quilt) {
2297 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2298 } else
2299 ret = (int)m_pQuilt->GetRefNativeScale();
2300
2301 return ret;
2302}
2303
2304ChartBase *ChartCanvas::GetChartAtCursor() {
2305 ChartBase *target_chart;
2306 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2307 target_chart = m_singleChart;
2308 else if (VPoint.b_quilt)
2309 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2310 else
2311 target_chart = NULL;
2312 return target_chart;
2313}
2314
2315ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2316 ChartBase *target_chart;
2317 if (VPoint.b_quilt)
2318 target_chart =
2319 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2320 else
2321 target_chart = NULL;
2322 return target_chart;
2323}
2324
2325int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2326 int new_dbIndex = -1;
2327 if (!VPoint.b_quilt) {
2328 if (m_pCurrentStack) {
2329 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2330 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2331 if (sc >= scale) {
2332 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2333 break;
2334 }
2335 }
2336 }
2337 } else {
2338 // Using the current quilt, select a useable reference chart
2339 // Said chart will be in the extended (possibly full-screen) stack,
2340 // And will have a scale equal to or just greater than the stipulated
2341 // value
2342 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2343 if (im > 0) {
2344 for (unsigned int is = 0; is < im; is++) {
2345 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2346 m_pQuilt->GetExtendedStackIndexArray()[is]);
2347 if ((m.Scale_ge(
2348 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2349 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2350 break;
2351 }
2352 }
2353 }
2354 }
2355
2356 return new_dbIndex;
2357}
2358
2359void ChartCanvas::EnablePaint(bool b_enable) {
2360 m_b_paint_enable = b_enable;
2361#ifdef ocpnUSE_GL
2362 if (m_glcc) m_glcc->EnablePaint(b_enable);
2363#endif
2364}
2365
2366bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2367
2368void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2369
2370std::vector<int> ChartCanvas::GetQuiltIndexArray(void) {
2371 return m_pQuilt->GetQuiltIndexArray();
2372 ;
2373}
2374
2375void ChartCanvas::SetQuiltMode(bool b_quilt) {
2376 VPoint.b_quilt = b_quilt;
2377 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2378}
2379
2380bool ChartCanvas::GetQuiltMode(void) { return VPoint.b_quilt; }
2381
2382int ChartCanvas::GetQuiltReferenceChartIndex(void) {
2383 return m_pQuilt->GetRefChartdbIndex();
2384}
2385
2386void ChartCanvas::InvalidateAllQuiltPatchs(void) {
2387 m_pQuilt->InvalidateAllQuiltPatchs();
2388}
2389
2390ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2391 return m_pQuilt->GetLargestScaleChart();
2392}
2393
2394ChartBase *ChartCanvas::GetFirstQuiltChart() {
2395 return m_pQuilt->GetFirstChart();
2396}
2397
2398ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2399
2400int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2401
2402void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2403 m_pQuilt->SetHiliteIndex(dbIndex);
2404}
2405
2406void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2407 m_pQuilt->SetHiliteIndexArray(hilite_array);
2408}
2409
2410void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2411 m_pQuilt->ClearHiliteIndexArray();
2412}
2413
2414std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2415 bool flag2) {
2416 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2417}
2418
2419int ChartCanvas::GetQuiltRefChartdbIndex(void) {
2420 return m_pQuilt->GetRefChartdbIndex();
2421}
2422
2423std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2424 return m_pQuilt->GetExtendedStackIndexArray();
2425}
2426
2427std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2428 return m_pQuilt->GetFullscreenIndexArray();
2429}
2430
2431std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2432 return m_pQuilt->GetEclipsedStackIndexArray();
2433}
2434
2435void ChartCanvas::InvalidateQuilt(void) { return m_pQuilt->Invalidate(); }
2436
2437double ChartCanvas::GetQuiltMaxErrorFactor() {
2438 return m_pQuilt->GetMaxErrorFactor();
2439}
2440
2441bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2442 return m_pQuilt->IsChartQuiltableRef(db_index);
2443}
2444
2445bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2446 double chartMaxScale =
2447 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2448 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2449}
2450
2451void ChartCanvas::StartMeasureRoute() {
2452 if (!m_routeState) { // no measure tool if currently creating route
2453 if (m_bMeasure_Active) {
2454 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2455 m_pMeasureRoute = NULL;
2456 }
2457
2458 m_bMeasure_Active = true;
2459 m_nMeasureState = 1;
2460 m_bDrawingRoute = false;
2461
2462 SetCursor(*pCursorPencil);
2463 Refresh();
2464 }
2465}
2466
2467void ChartCanvas::CancelMeasureRoute() {
2468 m_bMeasure_Active = false;
2469 m_nMeasureState = 0;
2470 m_bDrawingRoute = false;
2471
2472 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2473 m_pMeasureRoute = NULL;
2474
2475 SetCursor(*pCursorArrow);
2476}
2477
2478ViewPort &ChartCanvas::GetVP() { return VPoint; }
2479
2480void ChartCanvas::SetVP(ViewPort &vp) {
2481 VPoint = vp;
2482 VPoint.SetPixelScale(m_displayScale);
2483}
2484
2485// void ChartCanvas::SetFocus()
2486// {
2487// printf("set %d\n", m_canvasIndex);
2488// //wxWindow:SetFocus();
2489// }
2490
2491void ChartCanvas::TriggerDeferredFocus() {
2492 // #if defined(__WXGTK__) || defined(__WXOSX__)
2493
2494 m_deferredFocusTimer.Start(20, true);
2495
2496#if defined(__WXGTK__) || defined(__WXOSX__)
2497 gFrame->Raise();
2498#endif
2499
2500 // gFrame->Raise();
2501 // #else
2502 // SetFocus();
2503 // Refresh(true);
2504 // #endif
2505}
2506
2507void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2508 SetFocus();
2509 Refresh(true);
2510}
2511
2512void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2513 if (SendKeyEventToPlugins(event))
2514 return; // PlugIn did something, and does not want the canvas to do
2515 // anything else
2516
2517 int key_char = event.GetKeyCode();
2518 switch (key_char) {
2519 case '?':
2520 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2521 break;
2522 case '+':
2523 ZoomCanvas(g_plus_minus_zoom_factor, false);
2524 break;
2525 case '-':
2526 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2527 break;
2528 default:
2529 break;
2530 }
2531 if (g_benable_rotate) {
2532 switch (key_char) {
2533 case ']':
2534 RotateCanvas(1);
2535 Refresh();
2536 break;
2537
2538 case '[':
2539 RotateCanvas(-1);
2540 Refresh();
2541 break;
2542
2543 case '\\':
2544 DoRotateCanvas(0);
2545 break;
2546 }
2547 }
2548
2549 event.Skip();
2550}
2551
2552void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2553 if (SendKeyEventToPlugins(event))
2554 return; // PlugIn did something, and does not want the canvas to do
2555 // anything else
2556
2557 bool b_handled = false;
2558
2559 m_modkeys = event.GetModifiers();
2560
2561 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2562
2563#ifdef OCPN_ALT_MENUBAR
2564#ifndef __WXOSX__
2565 // If the permanent menubar is disabled, we show it temporarily when Alt is
2566 // pressed or when Alt + a letter is presssed (for the top-menu-level
2567 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2568 // some special cases.
2569 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2570 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2571 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2572 if (!g_bTempShowMenuBar) {
2573 g_bTempShowMenuBar = true;
2574 parent_frame->ApplyGlobalSettings(false);
2575 }
2576 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2577 event.Skip();
2578 return;
2579 }
2580 // If another key is pressed while Alt is down, do NOT toggle the menus when
2581 // Alt is released
2582 if (event.GetKeyCode() != WXK_ALT) {
2583 m_bMayToggleMenuBar = false;
2584 }
2585 }
2586#endif
2587#endif
2588
2589 // HOTKEYS
2590 switch (event.GetKeyCode()) {
2591 case WXK_TAB:
2592 // parent_frame->SwitchKBFocus( this );
2593 break;
2594
2595 case WXK_MENU:
2596 int x, y;
2597 event.GetPosition(&x, &y);
2598 m_FinishRouteOnKillFocus = false;
2599 CallPopupMenu(x, y);
2600 m_FinishRouteOnKillFocus = true;
2601 break;
2602
2603 case WXK_ALT:
2604 m_modkeys |= wxMOD_ALT;
2605 break;
2606
2607 case WXK_CONTROL:
2608 m_modkeys |= wxMOD_CONTROL;
2609 break;
2610
2611#ifdef __WXOSX__
2612 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2613 case WXK_RAW_CONTROL:
2614 m_modkeys |= wxMOD_RAW_CONTROL;
2615 break;
2616#endif
2617
2618 case WXK_LEFT:
2619 if (m_modkeys == wxMOD_CONTROL)
2620 parent_frame->DoStackDown(this);
2621 else if (g_bsmoothpanzoom) {
2622 StartTimedMovement();
2623 m_panx = -1;
2624 } else {
2625 PanCanvas(-panspeed, 0);
2626 }
2627 b_handled = true;
2628 break;
2629
2630 case WXK_UP:
2631 if (g_bsmoothpanzoom) {
2632 StartTimedMovement();
2633 m_pany = -1;
2634 } else
2635 PanCanvas(0, -panspeed);
2636 b_handled = true;
2637 break;
2638
2639 case WXK_RIGHT:
2640 if (m_modkeys == wxMOD_CONTROL)
2641 parent_frame->DoStackUp(this);
2642 else if (g_bsmoothpanzoom) {
2643 StartTimedMovement();
2644 m_panx = 1;
2645 } else
2646 PanCanvas(panspeed, 0);
2647 b_handled = true;
2648
2649 break;
2650
2651 case WXK_DOWN:
2652 if (g_bsmoothpanzoom) {
2653 StartTimedMovement();
2654 m_pany = 1;
2655 } else
2656 PanCanvas(0, panspeed);
2657 b_handled = true;
2658 break;
2659
2660 case WXK_F2:
2661 TogglebFollow();
2662 break;
2663
2664 case WXK_F3: {
2665 SetShowENCText(!GetShowENCText());
2666 Refresh(true);
2667 InvalidateGL();
2668 break;
2669 }
2670 case WXK_F4:
2671 if (!m_bMeasure_Active) {
2672 if (event.ShiftDown())
2673 m_bMeasure_DistCircle = true;
2674 else
2675 m_bMeasure_DistCircle = false;
2676
2677 StartMeasureRoute();
2678 } else {
2679 CancelMeasureRoute();
2680
2681 SetCursor(*pCursorArrow);
2682
2683 // SurfaceToolbar();
2684 InvalidateGL();
2685 Refresh(false);
2686 }
2687
2688 break;
2689
2690 case WXK_F5:
2691 parent_frame->ToggleColorScheme();
2692 gFrame->Raise();
2693 TriggerDeferredFocus();
2694 break;
2695
2696 case WXK_F6: {
2697 int mod = m_modkeys & wxMOD_SHIFT;
2698 if (mod != m_brightmod) {
2699 m_brightmod = mod;
2700 m_bbrightdir = !m_bbrightdir;
2701 }
2702
2703 if (!m_bbrightdir) {
2704 g_nbrightness -= 10;
2705 if (g_nbrightness <= MIN_BRIGHT) {
2706 g_nbrightness = MIN_BRIGHT;
2707 m_bbrightdir = true;
2708 }
2709 } else {
2710 g_nbrightness += 10;
2711 if (g_nbrightness >= MAX_BRIGHT) {
2712 g_nbrightness = MAX_BRIGHT;
2713 m_bbrightdir = false;
2714 }
2715 }
2716
2717 SetScreenBrightness(g_nbrightness);
2718 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2719
2720 SetFocus(); // just in case the external program steals it....
2721 gFrame->Raise(); // And reactivate the application main
2722
2723 break;
2724 }
2725
2726 case WXK_F7:
2727 parent_frame->DoStackDown(this);
2728 break;
2729
2730 case WXK_F8:
2731 parent_frame->DoStackUp(this);
2732 break;
2733
2734#ifndef __WXOSX__
2735 case WXK_F9: {
2736 ToggleCanvasQuiltMode();
2737 break;
2738 }
2739#endif
2740
2741 case WXK_F11:
2742 parent_frame->ToggleFullScreen();
2743 b_handled = true;
2744 break;
2745
2746 case WXK_F12: {
2747 if (m_modkeys == wxMOD_ALT) {
2748 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2749 } else {
2750 ToggleChartOutlines();
2751 }
2752 break;
2753 }
2754
2755 case WXK_PAUSE: // Drop MOB
2756 parent_frame->ActivateMOB();
2757 break;
2758
2759 // NUMERIC PAD
2760 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2761 case WXK_PAGEUP: {
2762 ZoomCanvas(g_plus_minus_zoom_factor, false);
2763 break;
2764 }
2765 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2766 case WXK_PAGEDOWN: {
2767 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2768 break;
2769 }
2770 case WXK_DELETE:
2771 case WXK_BACK:
2772 if (m_bMeasure_Active) {
2773 if (m_nMeasureState > 2) {
2774 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2775 m_pMeasureRoute->m_lastMousePointIndex =
2776 m_pMeasureRoute->GetnPoints();
2777 m_nMeasureState--;
2778 gFrame->RefreshAllCanvas();
2779 } else {
2780 CancelMeasureRoute();
2781 StartMeasureRoute();
2782 }
2783 }
2784 break;
2785 default:
2786 break;
2787 }
2788
2789 if (event.GetKeyCode() < 128) // ascii
2790 {
2791 int key_char = event.GetKeyCode();
2792
2793 // Handle both QWERTY and AZERTY keyboard separately for a few control
2794 // codes
2795 if (!g_b_assume_azerty) {
2796#ifdef __WXMAC__
2797 if (g_benable_rotate) {
2798 switch (key_char) {
2799 // On other platforms these are handled in OnKeyChar, which
2800 // (apparently) works better in some locales. On OS X it is better
2801 // to handle them here, since pressing Alt (which should change the
2802 // rotation speed) changes the key char and so prevents the keys
2803 // from working.
2804 case ']':
2805 RotateCanvas(1);
2806 b_handled = true;
2807 break;
2808
2809 case '[':
2810 RotateCanvas(-1);
2811 b_handled = true;
2812 break;
2813
2814 case '\\':
2815 DoRotateCanvas(0);
2816 b_handled = true;
2817 break;
2818 }
2819 }
2820#endif
2821 } else { // AZERTY
2822 switch (key_char) {
2823 case 43:
2824 ZoomCanvas(g_plus_minus_zoom_factor, false);
2825 break;
2826
2827 case 54: // '-' alpha/num pad
2828 // case 56: // '_' alpha/num pad
2829 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2830 break;
2831 }
2832 }
2833
2834#ifdef __WXOSX__
2835 // Ctrl+Cmd+F toggles fullscreen on macOS
2836 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
2837 m_modkeys & wxMOD_RAW_CONTROL) {
2838 parent_frame->ToggleFullScreen();
2839 return;
2840 }
2841#endif
2842
2843 if (event.ControlDown()) key_char -= 64;
2844
2845 if (key_char >= '0' && key_char <= '9')
2846 SetGroupIndex(key_char - '0');
2847 else
2848
2849 switch (key_char) {
2850 case 'A':
2851 SetShowENCAnchor(!GetShowENCAnchor());
2852 ReloadVP();
2853
2854 break;
2855
2856 case 'C':
2857 parent_frame->ToggleColorScheme();
2858 break;
2859
2860 case 'D': {
2861 int x, y;
2862 event.GetPosition(&x, &y);
2863 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
2864 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
2865 // First find out what kind of chart is being used
2866 if (!pPopupDetailSlider) {
2867 if (VPoint.b_quilt) {
2868 if (m_pQuilt) {
2869 if (m_pQuilt->GetChartAtPix(
2870 VPoint,
2871 wxPoint(
2872 x, y))) // = null if no chart loaded for this point
2873 {
2874 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2875 ->GetChartType();
2876 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2877 ->GetChartFamily();
2878 }
2879 }
2880 } else {
2881 if (m_singleChart) {
2882 ChartType = m_singleChart->GetChartType();
2883 ChartFam = m_singleChart->GetChartFamily();
2884 }
2885 }
2886 // If a charttype is found show the popupslider
2887 if ((ChartType != CHART_TYPE_UNKNOWN) ||
2888 (ChartFam != CHART_FAMILY_UNKNOWN)) {
2890 this, -1, ChartType, ChartFam,
2891 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
2892 wxDefaultSize, wxSIMPLE_BORDER, "");
2894 }
2895 } else //( !pPopupDetailSlider ) close popupslider
2896 {
2898 pPopupDetailSlider = NULL;
2899 }
2900 break;
2901 }
2902
2903 case 'E':
2904 m_nmea_log->Show();
2905 m_nmea_log->Raise();
2906 break;
2907
2908 case 'L':
2909 SetShowENCLights(!GetShowENCLights());
2910 ReloadVP();
2911
2912 break;
2913
2914 case 'M':
2915 if (event.ShiftDown())
2916 m_bMeasure_DistCircle = true;
2917 else
2918 m_bMeasure_DistCircle = false;
2919
2920 StartMeasureRoute();
2921 break;
2922
2923 case 'N':
2924 if (g_bInlandEcdis && ps52plib) {
2925 SetENCDisplayCategory((_DisCat)STANDARD);
2926 }
2927 break;
2928
2929 case 'O':
2930 ToggleChartOutlines();
2931 break;
2932
2933 case 'Q':
2934 ToggleCanvasQuiltMode();
2935 break;
2936
2937 case 'P':
2938 parent_frame->ToggleTestPause();
2939 break;
2940 case 'R':
2941 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
2942 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
2943 g_iNavAidRadarRingsNumberVisible = 1;
2944 else if (!g_bNavAidRadarRingsShown &&
2945 g_iNavAidRadarRingsNumberVisible == 1)
2946 g_iNavAidRadarRingsNumberVisible = 0;
2947 break;
2948 case 'S':
2949 SetShowENCDepth(!m_encShowDepth);
2950 ReloadVP();
2951 break;
2952
2953 case 'T':
2954 SetShowENCText(!GetShowENCText());
2955 ReloadVP();
2956 break;
2957
2958 case 'U':
2959 SetShowENCDataQual(!GetShowENCDataQual());
2960 ReloadVP();
2961 break;
2962
2963 case 'V':
2964 m_bShowNavobjects = !m_bShowNavobjects;
2965 Refresh(true);
2966 break;
2967
2968 case 'W': // W Toggle CPA alarm
2969 ToggleCPAWarn();
2970
2971 break;
2972
2973 case 1: // Ctrl A
2974 TogglebFollow();
2975
2976 break;
2977
2978 case 2: // Ctrl B
2979 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
2980 break;
2981
2982 case 13: // Ctrl M // Drop Marker at cursor
2983 {
2984 if (event.ControlDown()) gFrame->DropMarker(false);
2985 break;
2986 }
2987
2988 case 14: // Ctrl N - Activate next waypoint in a route
2989 {
2990 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
2991 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
2992 if ((indexActive + 1) <= r->GetnPoints()) {
2993 g_pRouteMan->ActivateNextPoint(r, true);
2994 InvalidateGL();
2995 Refresh(false);
2996 }
2997 }
2998 break;
2999 }
3000
3001 case 15: // Ctrl O - Drop Marker at boat's position
3002 {
3003 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3004 break;
3005 }
3006
3007 case 32: // Special needs use space bar
3008 {
3009 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3010 break;
3011 }
3012
3013 case -32: // Ctrl Space // Drop MOB
3014 {
3015 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3016
3017 break;
3018 }
3019
3020 case -20: // Ctrl ,
3021 {
3022 parent_frame->DoSettings();
3023 break;
3024 }
3025 case 17: // Ctrl Q
3026 parent_frame->Close();
3027 return;
3028
3029 case 18: // Ctrl R
3030 StartRoute();
3031 return;
3032
3033 case 20: // Ctrl T
3034 if (NULL == pGoToPositionDialog) // There is one global instance of
3035 // the Go To Position Dialog
3036 pGoToPositionDialog = new GoToPositionDialog(this);
3037 pGoToPositionDialog->SetCanvas(this);
3038 pGoToPositionDialog->Show();
3039 break;
3040
3041 case 25: // Ctrl Y
3042 if (undo->AnythingToRedo()) {
3043 undo->RedoNextAction();
3044 InvalidateGL();
3045 Refresh(false);
3046 }
3047 break;
3048
3049 case 26:
3050 if (event.ShiftDown()) { // Shift-Ctrl-Z
3051 if (undo->AnythingToRedo()) {
3052 undo->RedoNextAction();
3053 InvalidateGL();
3054 Refresh(false);
3055 }
3056 } else { // Ctrl Z
3057 if (undo->AnythingToUndo()) {
3058 undo->UndoLastAction();
3059 InvalidateGL();
3060 Refresh(false);
3061 }
3062 }
3063 break;
3064
3065 case 27:
3066 // Generic break
3067 if (m_bMeasure_Active) {
3068 CancelMeasureRoute();
3069
3070 SetCursor(*pCursorArrow);
3071
3072 // SurfaceToolbar();
3073 gFrame->RefreshAllCanvas();
3074 }
3075
3076 if (m_routeState) // creating route?
3077 {
3078 FinishRoute();
3079 // SurfaceToolbar();
3080 InvalidateGL();
3081 Refresh(false);
3082 }
3083
3084 break;
3085
3086 case 7: // Ctrl G
3087 switch (gamma_state) {
3088 case (0):
3089 r_gamma_mult = 0;
3090 g_gamma_mult = 1;
3091 b_gamma_mult = 0;
3092 gamma_state = 1;
3093 break;
3094 case (1):
3095 r_gamma_mult = 1;
3096 g_gamma_mult = 0;
3097 b_gamma_mult = 0;
3098 gamma_state = 2;
3099 break;
3100 case (2):
3101 r_gamma_mult = 1;
3102 g_gamma_mult = 1;
3103 b_gamma_mult = 1;
3104 gamma_state = 0;
3105 break;
3106 }
3107 SetScreenBrightness(g_nbrightness);
3108
3109 break;
3110
3111 case 9: // Ctrl I
3112 if (event.ControlDown()) {
3113 m_bShowCompassWin = !m_bShowCompassWin;
3114 SetShowGPSCompassWindow(m_bShowCompassWin);
3115 Refresh(false);
3116 }
3117 break;
3118
3119 default:
3120 break;
3121
3122 } // switch
3123 }
3124
3125 // Allow OnKeyChar to catch the key events too.
3126 if (!b_handled) {
3127 event.Skip();
3128 }
3129}
3130
3131void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3132 if (SendKeyEventToPlugins(event))
3133 return; // PlugIn did something, and does not want the canvas to do
3134 // anything else
3135
3136 switch (event.GetKeyCode()) {
3137 case WXK_TAB:
3138 parent_frame->SwitchKBFocus(this);
3139 break;
3140
3141 case WXK_LEFT:
3142 case WXK_RIGHT:
3143 m_panx = 0;
3144 if (!m_pany) m_panspeed = 0;
3145 break;
3146
3147 case WXK_UP:
3148 case WXK_DOWN:
3149 m_pany = 0;
3150 if (!m_panx) m_panspeed = 0;
3151 break;
3152
3153 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3154 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3155 case WXK_PAGEUP:
3156 case WXK_PAGEDOWN:
3157 if (m_mustmove) DoMovement(m_mustmove);
3158
3159 m_zoom_factor = 1;
3160 break;
3161
3162 case WXK_ALT:
3163 m_modkeys &= ~wxMOD_ALT;
3164#ifdef OCPN_ALT_MENUBAR
3165#ifndef __WXOSX__
3166 // If the permanent menu bar is disabled, and we are not in the middle of
3167 // another key combo, then show the menu bar temporarily when Alt is
3168 // released (or hide it if already visible).
3169 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3170 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3171 parent_frame->ApplyGlobalSettings(false);
3172 }
3173 m_bMayToggleMenuBar = true;
3174#endif
3175#endif
3176 break;
3177
3178 case WXK_CONTROL:
3179 m_modkeys &= ~wxMOD_CONTROL;
3180 break;
3181 }
3182
3183 if (event.GetKeyCode() < 128) // ascii
3184 {
3185 int key_char = event.GetKeyCode();
3186
3187 // Handle both QWERTY and AZERTY keyboard separately for a few control
3188 // codes
3189 if (!g_b_assume_azerty) {
3190 switch (key_char) {
3191 case '+':
3192 case '=':
3193 case '-':
3194 case '_':
3195 case 54:
3196 case 56: // '_' alpha/num pad
3197 DoMovement(m_mustmove);
3198
3199 // m_zoom_factor = 1;
3200 break;
3201 case '[':
3202 case ']':
3203 DoMovement(m_mustmove);
3204 m_rotation_speed = 0;
3205 break;
3206 }
3207 } else {
3208 switch (key_char) {
3209 case 43:
3210 case 54: // '-' alpha/num pad
3211 case 56: // '_' alpha/num pad
3212 DoMovement(m_mustmove);
3213
3214 m_zoom_factor = 1;
3215 break;
3216 }
3217 }
3218 }
3219 event.Skip();
3220}
3221
3222void ChartCanvas::ToggleChartOutlines(void) {
3223 m_bShowOutlines = !m_bShowOutlines;
3224
3225 Refresh(false);
3226
3227#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3228 // needs a full refresh
3229 if (g_bopengl) InvalidateGL();
3230#endif
3231}
3232
3233void ChartCanvas::ToggleLookahead() {
3234 m_bLookAhead = !m_bLookAhead;
3235 m_OSoffsetx = 0; // center ownship
3236 m_OSoffsety = 0;
3237}
3238
3239void ChartCanvas::SetUpMode(int mode) {
3240 m_upMode = mode;
3241
3242 if (mode != NORTH_UP_MODE) {
3243 // Stuff the COGAvg table in case COGUp is selected
3244 double stuff = 0;
3245 if (!std::isnan(gCog)) stuff = gCog;
3246
3247 if (g_COGAvgSec > 0) {
3248 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3249 }
3250 g_COGAvg = stuff;
3251 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3252 } else {
3253 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3254 SetVPRotation(GetVPSkew());
3255 else
3256 SetVPRotation(0); /* reset to north up */
3257 }
3258
3259 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3260 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3261
3262 UpdateGPSCompassStatusBox(true);
3263 gFrame->DoChartUpdate();
3264}
3265
3266bool ChartCanvas::DoCanvasCOGSet(void) {
3267 if (GetUpMode() == NORTH_UP_MODE) return false;
3268 double cog_use = g_COGAvg;
3269 if (g_btenhertz) cog_use = gCog;
3270
3271 double rotation = 0;
3272 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3273 rotation = -gHdt * PI / 180.;
3274 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3275 rotation = -cog_use * PI / 180.;
3276
3277 SetVPRotation(rotation);
3278 return true;
3279}
3280
3281double easeOutCubic(double t) {
3282 // Starts quickly and slows down toward the end
3283 return 1.0 - pow(1.0 - t, 3.0);
3284}
3285
3286void ChartCanvas::StartChartDragInertia() {
3287 m_bChartDragging = false;
3288
3289 // Set some parameters
3290 m_chart_drag_inertia_time = 750; // msec
3291 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3292 m_last_elapsed = 0;
3293
3294 // Calculate ending drag velocity
3295 size_t n_vel = 10;
3296 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3297 int xacc = 0;
3298 int yacc = 0;
3299 double tacc = 0;
3300 size_t length = m_drag_vec_t.size();
3301 for (size_t i = 0; i < n_vel; i++) {
3302 xacc += m_drag_vec_x.at(length - 1 - i);
3303 yacc += m_drag_vec_y.at(length - 1 - i);
3304 tacc += m_drag_vec_t.at(length - 1 - i);
3305 }
3306 m_chart_drag_velocity_x = xacc / tacc;
3307 m_chart_drag_velocity_y = yacc / tacc;
3308
3309 m_chart_drag_inertia_active = true;
3310 // First callback as fast as possible.
3311 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3312}
3313
3314void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3315 if (!m_chart_drag_inertia_active) return;
3316
3317 // Calculate time fraction from 0..1
3318 wxLongLong now = wxGetLocalTimeMillis();
3319 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3320 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3321 if (t > 1.0) t = 1.0;
3322 double e = 1.0 - easeOutCubic(t); // 0..1
3323
3324 double dx =
3325 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3326 double dy =
3327 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3328
3329 m_last_elapsed = elapsed;
3330
3331 // Ensure that target destination lies on whole-pixel boundary
3332 // This allows the render engine to use a faster FBO copy method for drawing
3333 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3334 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3335 double inertia_lat, inertia_lon;
3336 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3337 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3338 // Check if ownship has moved off-screen
3339 if (!IsOwnshipOnScreen()) {
3340 m_bFollow = false; // update the follow flag
3341 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3342 UpdateFollowButtonState();
3343 m_OSoffsetx = 0;
3344 m_OSoffsety = 0;
3345 } else {
3346 m_OSoffsetx += dx;
3347 m_OSoffsety -= dy;
3348 }
3349
3350 Refresh(false);
3351
3352 // Stop condition
3353 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3354 m_chart_drag_inertia_timer.Stop();
3355
3356 // Disable chart pan movement logic
3357 m_target_lat = GetVP().clat;
3358 m_target_lon = GetVP().clon;
3359 m_pan_drag.x = m_pan_drag.y = 0;
3360 m_panx = m_pany = 0;
3361 m_chart_drag_inertia_active = false;
3362 DoCanvasUpdate();
3363
3364 } else {
3365 int target_redraw_interval = 40; // msec
3366 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3367 }
3368}
3369
3370void ChartCanvas::StopMovement() {
3371 m_panx = m_pany = 0;
3372 m_panspeed = 0;
3373 m_zoom_factor = 1;
3374 m_rotation_speed = 0;
3375 m_mustmove = 0;
3376#if 0
3377#if !defined(__WXGTK__) && !defined(__WXQT__)
3378 SetFocus();
3379 gFrame->Raise();
3380#endif
3381#endif
3382}
3383
3384/* instead of integrating in timer callbacks
3385 (which do not always get called fast enough)
3386 we can perform the integration of movement
3387 at each render frame based on the time change */
3388bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3389 // Start/restart the stop movement timer
3390 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3391
3392 if (!pMovementTimer->IsRunning()) {
3393 // printf("timer not running, starting\n");
3394 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3395 }
3396
3397 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3398 // already moving, gets called again because of key-repeat event
3399 return false;
3400 }
3401
3402 m_last_movement_time = wxDateTime::UNow();
3403
3404 return true;
3405}
3406void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3407 int nstep) {
3408 // Save the target
3409 m_target_lat = target_lat;
3410 m_target_lon = target_lon;
3411
3412 // Save the start point
3413 m_start_lat = GetVP().clat;
3414 m_start_lon = GetVP().clon;
3415
3416 m_VPMovementTimer.Start(1, true); // oneshot
3417 m_timed_move_vp_active = true;
3418 m_stvpc = 0;
3419 m_timedVP_step = nstep;
3420}
3421
3422void ChartCanvas::DoTimedMovementVP() {
3423 if (!m_timed_move_vp_active) return; // not active
3424 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3425 StopMovement();
3426 return;
3427 }
3428 // Stop condition
3429 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3430 double d2 =
3431 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3432 d2 = pow(d2, 0.5);
3433
3434 if (d2 < one_pix) {
3435 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3436 StopMovementVP();
3437 return;
3438 }
3439
3440 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3441 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3442 // StopMovementVP();
3443 // return;
3444 // }
3445
3446 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3447 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3448
3449 m_run_lat = new_lat;
3450 m_run_lon = new_lon;
3451
3452 // printf(" Timed\n");
3453 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3454}
3455
3456void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3457
3458void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3459
3460void ChartCanvas::StartTimedMovementTarget() {}
3461
3462void ChartCanvas::DoTimedMovementTarget() {}
3463
3464void ChartCanvas::StopMovementTarget() {}
3465
3466void ChartCanvas::DoTimedMovement() {
3467 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3468 !m_rotation_speed)
3469 return; /* not moving */
3470
3471 wxDateTime now = wxDateTime::UNow();
3472 long dt = 0;
3473 if (m_last_movement_time.IsValid())
3474 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3475
3476 m_last_movement_time = now;
3477
3478 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3479 dt = 500;
3480
3481 DoMovement(dt);
3482}
3483
3485 /* if we get here quickly assume 1ms so that some movement occurs */
3486 if (dt == 0) dt = 1;
3487
3488 m_mustmove -= dt;
3489 if (m_mustmove < 0) m_mustmove = 0;
3490
3491 if (m_pan_drag.x || m_pan_drag.y) {
3492 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3493 m_pan_drag.x = m_pan_drag.y = 0;
3494 }
3495
3496 if (m_panx || m_pany) {
3497 const double slowpan = .1, maxpan = 2;
3498 if (m_modkeys == wxMOD_ALT)
3499 m_panspeed = slowpan;
3500 else {
3501 m_panspeed += (double)dt / 500; /* apply acceleration */
3502 m_panspeed = wxMin(maxpan, m_panspeed);
3503 }
3504 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3505 }
3506
3507 if (m_zoom_factor != 1) {
3508 double alpha = 400, beta = 1.5;
3509 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3510
3511 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3512
3513 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3514
3515 // Try to hit the zoom target exactly.
3516 // if(m_wheelzoom_stop_oneshot > 0)
3517 {
3518 if (zoom_factor > 1) {
3519 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3520 zoom_factor = VPoint.chart_scale / m_zoom_target;
3521 }
3522
3523 else if (zoom_factor < 1) {
3524 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3525 zoom_factor = VPoint.chart_scale / m_zoom_target;
3526 }
3527 }
3528
3529 if (fabs(zoom_factor - 1) > 1e-4) {
3530 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3531 } else {
3532 StopMovement();
3533 }
3534
3535 if (m_wheelzoom_stop_oneshot > 0) {
3536 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3537 m_wheelzoom_stop_oneshot = 0;
3538 StopMovement();
3539 }
3540
3541 // Don't overshoot the zoom target.
3542 if (zoom_factor > 1) {
3543 if (VPoint.chart_scale <= m_zoom_target) {
3544 m_wheelzoom_stop_oneshot = 0;
3545 StopMovement();
3546 }
3547 } else if (zoom_factor < 1) {
3548 if (VPoint.chart_scale >= m_zoom_target) {
3549 m_wheelzoom_stop_oneshot = 0;
3550 StopMovement();
3551 }
3552 }
3553 }
3554 }
3555
3556 if (m_rotation_speed) { /* in degrees per second */
3557 double speed = m_rotation_speed;
3558 if (m_modkeys == wxMOD_ALT) speed /= 10;
3559 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3560 }
3561}
3562
3563void ChartCanvas::SetColorScheme(ColorScheme cs) {
3564 SetAlertString("");
3565
3566 // Setup ownship image pointers
3567 switch (cs) {
3568 case GLOBAL_COLOR_SCHEME_DAY:
3569 m_pos_image_red = &m_os_image_red_day;
3570 m_pos_image_grey = &m_os_image_grey_day;
3571 m_pos_image_yellow = &m_os_image_yellow_day;
3572 m_pos_image_user = m_pos_image_user_day;
3573 m_pos_image_user_grey = m_pos_image_user_grey_day;
3574 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3575 m_cTideBitmap = m_bmTideDay;
3576 m_cCurrentBitmap = m_bmCurrentDay;
3577
3578 break;
3579 case GLOBAL_COLOR_SCHEME_DUSK:
3580 m_pos_image_red = &m_os_image_red_dusk;
3581 m_pos_image_grey = &m_os_image_grey_dusk;
3582 m_pos_image_yellow = &m_os_image_yellow_dusk;
3583 m_pos_image_user = m_pos_image_user_dusk;
3584 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3585 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3586 m_cTideBitmap = m_bmTideDusk;
3587 m_cCurrentBitmap = m_bmCurrentDusk;
3588 break;
3589 case GLOBAL_COLOR_SCHEME_NIGHT:
3590 m_pos_image_red = &m_os_image_red_night;
3591 m_pos_image_grey = &m_os_image_grey_night;
3592 m_pos_image_yellow = &m_os_image_yellow_night;
3593 m_pos_image_user = m_pos_image_user_night;
3594 m_pos_image_user_grey = m_pos_image_user_grey_night;
3595 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3596 m_cTideBitmap = m_bmTideNight;
3597 m_cCurrentBitmap = m_bmCurrentNight;
3598 break;
3599 default:
3600 m_pos_image_red = &m_os_image_red_day;
3601 m_pos_image_grey = &m_os_image_grey_day;
3602 m_pos_image_yellow = &m_os_image_yellow_day;
3603 m_pos_image_user = m_pos_image_user_day;
3604 m_pos_image_user_grey = m_pos_image_user_grey_day;
3605 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3606 m_cTideBitmap = m_bmTideDay;
3607 m_cCurrentBitmap = m_bmCurrentDay;
3608 break;
3609 }
3610
3611 CreateDepthUnitEmbossMaps(cs);
3612 CreateOZEmbossMapData(cs);
3613
3614 // Set up fog effect base color
3615 m_fog_color = wxColor(
3616 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3617 float dim = 1.0;
3618 switch (cs) {
3619 case GLOBAL_COLOR_SCHEME_DUSK:
3620 dim = 0.5;
3621 break;
3622 case GLOBAL_COLOR_SCHEME_NIGHT:
3623 dim = 0.25;
3624 break;
3625 default:
3626 break;
3627 }
3628 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3629 m_fog_color.Blue() * dim);
3630
3631 // Really dark
3632#if 0
3633 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3634 SetBackgroundColour( wxColour(0,0,0) );
3635
3636 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3637 }
3638 else{
3639 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3640#ifndef __WXMAC__
3641 SetBackgroundColour( wxNullColour );
3642#endif
3643 }
3644#endif
3645
3646 // UpdateToolbarColorScheme(cs);
3647
3648 m_Piano->SetColorScheme(cs);
3649
3650 m_Compass->SetColorScheme(cs);
3651
3652 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3653
3654 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3655
3656 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3657 if (m_notification_button) {
3658 m_notification_button->SetColorScheme(cs);
3659 }
3660
3661#ifdef ocpnUSE_GL
3662 if (g_bopengl && m_glcc) {
3663 m_glcc->SetColorScheme(cs);
3664 g_glTextureManager->ClearAllRasterTextures();
3665 // m_glcc->FlushFBO();
3666 }
3667#endif
3668 SetbTCUpdate(true); // force re-render of tide/current locators
3669 m_brepaint_piano = true;
3670
3671 ReloadVP();
3672
3673 m_cs = cs;
3674}
3675
3676wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3677 wxImage img = Bitmap.ConvertToImage();
3678 int sx = img.GetWidth();
3679 int sy = img.GetHeight();
3680
3681 wxImage new_img(img);
3682
3683 for (int i = 0; i < sx; i++) {
3684 for (int j = 0; j < sy; j++) {
3685 if (!img.IsTransparent(i, j)) {
3686 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3687 (unsigned char)(img.GetGreen(i, j) * factor),
3688 (unsigned char)(img.GetBlue(i, j) * factor));
3689 }
3690 }
3691 }
3692
3693 wxBitmap ret = wxBitmap(new_img);
3694
3695 return ret;
3696}
3697
3698void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3699 int max) {
3700 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3701 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3702
3703 if (!m_pBrightPopup) {
3704 // Calculate size
3705 int x, y;
3706 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3707
3708 m_pBrightPopup = new TimedPopupWin(this, 3);
3709
3710 m_pBrightPopup->SetSize(x, y);
3711 m_pBrightPopup->Move(120, 120);
3712 }
3713
3714 int bmpsx = m_pBrightPopup->GetSize().x;
3715 int bmpsy = m_pBrightPopup->GetSize().y;
3716
3717 wxBitmap bmp(bmpsx, bmpsx);
3718 wxMemoryDC mdc(bmp);
3719
3720 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3721 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3722 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3723 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3724 mdc.Clear();
3725
3726 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3727
3728 mdc.SetFont(*pfont);
3729 wxString val;
3730
3731 if (brightness == max)
3732 val = "MAX";
3733 else if (brightness == min)
3734 val = "MIN";
3735 else
3736 val.Printf("%3d", brightness);
3737
3738 mdc.DrawText(val, 0, 0);
3739
3740 mdc.SelectObject(wxNullBitmap);
3741
3742 m_pBrightPopup->SetBitmap(bmp);
3743 m_pBrightPopup->Show();
3744 m_pBrightPopup->Refresh();
3745}
3746
3747void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3748 m_b_rot_hidef = true;
3749 ReloadVP();
3750}
3751
3752void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3753 if (!g_bRollover) return;
3754
3755 bool b_need_refresh = false;
3756
3757 wxSize win_size = GetSize() * m_displayScale;
3758 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3759
3760 // Handle the AIS Rollover Window first
3761 bool showAISRollover = false;
3762 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3763 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3764 SelectItem *pFind = pSelectAIS->FindSelection(
3765 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3766 if (pFind) {
3767 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3768 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3769
3770 if (ptarget) {
3771 showAISRollover = true;
3772
3773 if (NULL == m_pAISRolloverWin) {
3774 m_pAISRolloverWin = new RolloverWin(this);
3775 m_pAISRolloverWin->IsActive(false);
3776 b_need_refresh = true;
3777 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3778 m_AISRollover_MMSI != FoundAIS_MMSI) {
3779 // Sometimes the mouse moves fast enough to get over a new AIS
3780 // target before the one-shot has fired to remove the old target.
3781 // Result: wrong target data is shown.
3782 // Detect this case,close the existing rollover ASAP, and restart
3783 // the timer.
3784 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3785 m_pAISRolloverWin->IsActive(false);
3786 m_AISRollover_MMSI = 0;
3787 Refresh();
3788 return;
3789 }
3790
3791 m_AISRollover_MMSI = FoundAIS_MMSI;
3792
3793 if (!m_pAISRolloverWin->IsActive()) {
3794 wxString s = ptarget->GetRolloverString();
3795 m_pAISRolloverWin->SetString(s);
3796
3797 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3798 AIS_ROLLOVER, win_size);
3799 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3800 m_pAISRolloverWin->IsActive(true);
3801 b_need_refresh = true;
3802 }
3803 }
3804 } else {
3805 m_AISRollover_MMSI = 0;
3806 showAISRollover = false;
3807 }
3808 }
3809
3810 // Maybe turn the rollover off
3811 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3812 m_pAISRolloverWin->IsActive(false);
3813 m_AISRollover_MMSI = 0;
3814 b_need_refresh = true;
3815 }
3816
3817 // Now the Route info rollover
3818 // Show the route segment info
3819 bool showRouteRollover = false;
3820
3821 if (NULL == m_pRolloverRouteSeg) {
3822 // Get a list of all selectable sgements, and search for the first
3823 // visible segment as the rollover target.
3824
3825 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3826 SelectableItemList SelList = pSelect->FindSelectionList(
3827 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3828 wxSelectableItemListNode *node = SelList.GetFirst();
3829 while (node) {
3830 SelectItem *pFindSel = node->GetData();
3831
3832 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3833
3834 if (pr && pr->IsVisible()) {
3835 m_pRolloverRouteSeg = pFindSel;
3836 showRouteRollover = true;
3837
3838 if (NULL == m_pRouteRolloverWin) {
3839 m_pRouteRolloverWin = new RolloverWin(this, 10);
3840 m_pRouteRolloverWin->IsActive(false);
3841 }
3842
3843 if (!m_pRouteRolloverWin->IsActive()) {
3844 wxString s;
3845 RoutePoint *segShow_point_a =
3846 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
3847 RoutePoint *segShow_point_b =
3848 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
3849
3850 double brg, dist;
3851 DistanceBearingMercator(
3852 segShow_point_b->m_lat, segShow_point_b->m_lon,
3853 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
3854
3855 if (!pr->m_bIsInLayer)
3856 s.Append(_("Route") + ": ");
3857 else
3858 s.Append(_("Layer Route: "));
3859
3860 if (pr->m_RouteNameString.IsEmpty())
3861 s.Append(_("(unnamed)"));
3862 else
3863 s.Append(pr->m_RouteNameString);
3864
3865 s << "\n"
3866 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
3867 << "\n"
3868 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
3869 << segShow_point_b->GetName() << "\n";
3870
3871 if (g_bShowTrue)
3872 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
3873 (int)floor(brg + 0.5), 0x00B0);
3874 if (g_bShowMag) {
3875 double latAverage =
3876 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
3877 double lonAverage =
3878 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
3879 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
3880
3881 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
3882 (int)floor(varBrg + 0.5), 0x00B0);
3883 }
3884
3885 s << FormatDistanceAdaptive(dist);
3886
3887 // Compute and display cumulative distance from route start point to
3888 // current leg end point and RNG,TTG,ETA from ship to current leg end
3889 // point for active route
3890 double shiptoEndLeg = 0.;
3891 bool validActive = false;
3892 if (pr->IsActive() &&
3893 pr->pRoutePointList->GetFirst()->GetData()->m_bIsActive)
3894 validActive = true;
3895
3896 if (segShow_point_a != pr->pRoutePointList->GetFirst()->GetData()) {
3897 wxRoutePointListNode *node =
3898 (pr->pRoutePointList)->GetFirst()->GetNext();
3899 RoutePoint *prp;
3900 float dist_to_endleg = 0;
3901 wxString t;
3902
3903 while (node) {
3904 prp = node->GetData();
3905 if (validActive)
3906 shiptoEndLeg += prp->m_seg_len;
3907 else if (prp->m_bIsActive)
3908 validActive = true;
3909 dist_to_endleg += prp->m_seg_len;
3910 if (prp->IsSame(segShow_point_a)) break;
3911 node = node->GetNext();
3912 }
3913 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
3914 }
3915 // write from ship to end selected leg point data if the route is
3916 // active
3917 if (validActive) {
3918 s << "\n"
3919 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
3920 shiptoEndLeg +=
3921 g_pRouteMan
3922 ->GetCurrentRngToActivePoint(); // add distance from ship
3923 // to active point
3924 shiptoEndLeg +=
3925 segShow_point_b
3926 ->m_seg_len; // add the lenght of the selected leg
3927 s << FormatDistanceAdaptive(shiptoEndLeg);
3928 // ensure sog/cog are valid and vmg is positive to keep data
3929 // coherent
3930 double vmg = 0.;
3931 if (!std::isnan(gCog) && !std::isnan(gSog))
3932 vmg = gSog *
3933 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
3934 PI / 180.);
3935 if (vmg > 0.) {
3936 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
3937 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
3938 s << " - "
3939 << wxString(ttg_sec > SECONDS_PER_DAY
3940 ? ttg_span.Format(_("%Dd %H:%M"))
3941 : ttg_span.Format(_("%H:%M")));
3942 wxDateTime dtnow, eta;
3943 eta = dtnow.SetToCurrent().Add(ttg_span);
3944 s << " - " << eta.Format("%b").Mid(0, 4)
3945 << eta.Format(" %d %H:%M");
3946 } else
3947 s << " ---- ----";
3948 }
3949 m_pRouteRolloverWin->SetString(s);
3950
3951 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3952 LEG_ROLLOVER, win_size);
3953 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
3954 m_pRouteRolloverWin->IsActive(true);
3955 b_need_refresh = true;
3956 showRouteRollover = true;
3957 break;
3958 }
3959 } else
3960 node = node->GetNext();
3961 }
3962 } else {
3963 // Is the cursor still in select radius, and not timed out?
3964 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3965 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
3966 m_pRolloverRouteSeg))
3967 showRouteRollover = false;
3968 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
3969 showRouteRollover = false;
3970 else
3971 showRouteRollover = true;
3972 }
3973
3974 // If currently creating a route, do not show this rollover window
3975 if (m_routeState) showRouteRollover = false;
3976
3977 // Similar for AIS target rollover window
3978 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
3979 showRouteRollover = false;
3980
3981 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
3982 !showRouteRollover) {
3983 m_pRouteRolloverWin->IsActive(false);
3984 m_pRolloverRouteSeg = NULL;
3985 m_pRouteRolloverWin->Destroy();
3986 m_pRouteRolloverWin = NULL;
3987 b_need_refresh = true;
3988 } else if (m_pRouteRolloverWin && showRouteRollover) {
3989 m_pRouteRolloverWin->IsActive(true);
3990 b_need_refresh = true;
3991 }
3992
3993 // Now the Track info rollover
3994 // Show the track segment info
3995 bool showTrackRollover = false;
3996
3997 if (NULL == m_pRolloverTrackSeg) {
3998 // Get a list of all selectable sgements, and search for the first
3999 // visible segment as the rollover target.
4000
4001 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4002 SelectableItemList SelList = pSelect->FindSelectionList(
4003 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4004 wxSelectableItemListNode *node = SelList.GetFirst();
4005 while (node) {
4006 SelectItem *pFindSel = node->GetData();
4007
4008 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4009
4010 if (pt && pt->IsVisible()) {
4011 m_pRolloverTrackSeg = pFindSel;
4012 showTrackRollover = true;
4013
4014 if (NULL == m_pTrackRolloverWin) {
4015 m_pTrackRolloverWin = new RolloverWin(this, 10);
4016 m_pTrackRolloverWin->IsActive(false);
4017 }
4018
4019 if (!m_pTrackRolloverWin->IsActive()) {
4020 wxString s;
4021 TrackPoint *segShow_point_a =
4022 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4023 TrackPoint *segShow_point_b =
4024 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4025
4026 double brg, dist;
4027 DistanceBearingMercator(
4028 segShow_point_b->m_lat, segShow_point_b->m_lon,
4029 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4030
4031 if (!pt->m_bIsInLayer)
4032 s.Append(_("Track") + ": ");
4033 else
4034 s.Append(_("Layer Track: "));
4035
4036 if (pt->GetName().IsEmpty())
4037 s.Append(_("(unnamed)"));
4038 else
4039 s.Append(pt->GetName());
4040 double tlenght = pt->Length();
4041 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4042 if (pt->GetLastPoint()->GetTimeString() &&
4043 pt->GetPoint(0)->GetTimeString()) {
4044 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4045 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4046 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4047 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4048 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4049 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4050 << getUsrSpeedUnit();
4051 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4052 : ttime.Format(" %H:%M"));
4053 }
4054 }
4055
4056 if (g_bShowTrackPointTime &&
4057 strlen(segShow_point_b->GetTimeString())) {
4058 wxString stamp = segShow_point_b->GetTimeString();
4059 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4060 if (timestamp.IsValid()) {
4061 // Format track rollover timestamp to OCPN global TZ setting
4064 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4065 }
4066 s << "\n" << _("Segment Created: ") << stamp;
4067 }
4068
4069 s << "\n";
4070 if (g_bShowTrue)
4071 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4072 0x00B0);
4073
4074 if (g_bShowMag) {
4075 double latAverage =
4076 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4077 double lonAverage =
4078 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4079 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4080
4081 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4082 0x00B0);
4083 }
4084
4085 s << FormatDistanceAdaptive(dist);
4086
4087 if (segShow_point_a->GetTimeString() &&
4088 segShow_point_b->GetTimeString()) {
4089 wxDateTime apoint = segShow_point_a->GetCreateTime();
4090 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4091 if (apoint.IsValid() && bpoint.IsValid()) {
4092 double segmentSpeed = toUsrSpeed(
4093 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4094 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4095 << getUsrSpeedUnit();
4096 }
4097 }
4098
4099 m_pTrackRolloverWin->SetString(s);
4100
4101 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4102 LEG_ROLLOVER, win_size);
4103 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4104 m_pTrackRolloverWin->IsActive(true);
4105 b_need_refresh = true;
4106 showTrackRollover = true;
4107 break;
4108 }
4109 } else
4110 node = node->GetNext();
4111 }
4112 } else {
4113 // Is the cursor still in select radius, and not timed out?
4114 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4115 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4116 m_pRolloverTrackSeg))
4117 showTrackRollover = false;
4118 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4119 showTrackRollover = false;
4120 else
4121 showTrackRollover = true;
4122 }
4123
4124 // Similar for AIS target rollover window
4125 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4126 showTrackRollover = false;
4127
4128 // Similar for route rollover window
4129 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4130 showTrackRollover = false;
4131
4132 // TODO We onlt show tracks on primary canvas....
4133 // if(!IsPrimaryCanvas())
4134 // showTrackRollover = false;
4135
4136 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4137 !showTrackRollover) {
4138 m_pTrackRolloverWin->IsActive(false);
4139 m_pRolloverTrackSeg = NULL;
4140 m_pTrackRolloverWin->Destroy();
4141 m_pTrackRolloverWin = NULL;
4142 b_need_refresh = true;
4143 } else if (m_pTrackRolloverWin && showTrackRollover) {
4144 m_pTrackRolloverWin->IsActive(true);
4145 b_need_refresh = true;
4146 }
4147
4148 if (b_need_refresh) Refresh();
4149}
4150
4151void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4152 if ((GetShowENCLights() || m_bsectors_shown) &&
4153 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4154 extendedSectorLegs)) {
4155 if (!m_bsectors_shown) {
4156 ReloadVP(false);
4157 m_bsectors_shown = true;
4158 }
4159 } else {
4160 if (m_bsectors_shown) {
4161 ReloadVP(false);
4162 m_bsectors_shown = false;
4163 }
4164 }
4165
4166// This is here because GTK status window update is expensive..
4167// cairo using pango rebuilds the font every time so is very
4168// inefficient
4169// Anyway, only update the status bar when this timer expires
4170#if defined(__WXGTK__) || defined(__WXQT__)
4171 {
4172 // Check the absolute range of the cursor position
4173 // There could be a window wherein the chart geoereferencing is not
4174 // valid....
4175 double cursor_lat, cursor_lon;
4176 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4177
4178 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4179 while (cursor_lon < -180.) cursor_lon += 360.;
4180
4181 while (cursor_lon > 180.) cursor_lon -= 360.;
4182
4183 SetCursorStatus(cursor_lat, cursor_lon);
4184 }
4185 }
4186#endif
4187}
4188
4189void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4190 if (!parent_frame->m_pStatusBar) return;
4191
4192 wxString s1;
4193 s1 += " ";
4194 s1 += toSDMM(1, cursor_lat);
4195 s1 += " ";
4196 s1 += toSDMM(2, cursor_lon);
4197
4198 if (STAT_FIELD_CURSOR_LL >= 0)
4199 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4200
4201 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4202
4203 double brg, dist;
4204 wxString sm;
4205 wxString st;
4206 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4207 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4208 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4209
4210 wxString s = st + sm;
4211 s << FormatDistanceAdaptive(dist);
4212
4213 // CUSTOMIZATION - LIVE ETA OPTION
4214 // -------------------------------------------------------
4215 // Calculate an "live" ETA based on route starting from the current
4216 // position of the boat and goes to the cursor of the mouse.
4217 // In any case, an standard ETA will be calculated with a default speed
4218 // of the boat to give an estimation of the route (in particular if GPS
4219 // is off).
4220
4221 // Display only if option "live ETA" is selected in Settings > Display >
4222 // General.
4223 if (g_bShowLiveETA) {
4224 float realTimeETA;
4225 float boatSpeed;
4226 float boatSpeedDefault = g_defaultBoatSpeed;
4227
4228 // Calculate Estimate Time to Arrival (ETA) in minutes
4229 // Check before is value not closed to zero (it will make an very big
4230 // number...)
4231 if (!std::isnan(gSog)) {
4232 boatSpeed = gSog;
4233 if (boatSpeed < 0.5) {
4234 realTimeETA = 0;
4235 } else {
4236 realTimeETA = dist / boatSpeed * 60;
4237 }
4238 } else {
4239 realTimeETA = 0;
4240 }
4241
4242 // Add space after distance display
4243 s << " ";
4244 // Display ETA
4245 s << minutesToHoursDays(realTimeETA);
4246
4247 // In any case, display also an ETA with default speed at 6knts
4248
4249 s << " [@";
4250 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4251 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4252 s << " ";
4253 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4254 s << "]";
4255 }
4256 // END OF - LIVE ETA OPTION
4257
4258 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4259}
4260
4261// CUSTOMIZATION - FORMAT MINUTES
4262// -------------------------------------------------------
4263// New function to format minutes into a more readable format:
4264// * Hours + minutes, or
4265// * Days + hours.
4266wxString minutesToHoursDays(float timeInMinutes) {
4267 wxString s;
4268
4269 if (timeInMinutes == 0) {
4270 s << "--min";
4271 }
4272
4273 // Less than 60min, keep time in minutes
4274 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4275 s << wxString::Format("%d", (int)timeInMinutes);
4276 s << "min";
4277 }
4278
4279 // Between 1h and less than 24h, display time in hours, minutes
4280 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4281 int hours;
4282 int min;
4283 hours = (int)timeInMinutes / 60;
4284 min = (int)timeInMinutes % 60;
4285
4286 if (min == 0) {
4287 s << wxString::Format("%d", hours);
4288 s << "h";
4289 } else {
4290 s << wxString::Format("%d", hours);
4291 s << "h";
4292 s << wxString::Format("%d", min);
4293 s << "min";
4294 }
4295
4296 }
4297
4298 // More than 24h, display time in days, hours
4299 else if (timeInMinutes > 24 * 60) {
4300 int days;
4301 int hours;
4302 days = (int)(timeInMinutes / 60) / 24;
4303 hours = (int)(timeInMinutes / 60) % 24;
4304
4305 if (hours == 0) {
4306 s << wxString::Format("%d", days);
4307 s << "d";
4308 } else {
4309 s << wxString::Format("%d", days);
4310 s << "d";
4311 s << wxString::Format("%d", hours);
4312 s << "h";
4313 }
4314 }
4315
4316 return s;
4317}
4318
4319// END OF CUSTOMIZATION - FORMAT MINUTES
4320// Thanks open source code ;-)
4321// -------------------------------------------------------
4322
4323void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4324 double clat, clon;
4325 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4326 *lat = clat;
4327 *lon = clon;
4328}
4329
4330void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4331 wxPoint2DDouble *r) {
4332 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4333}
4334
4336 double rlon, wxPoint2DDouble *r) {
4337 // If the Current Chart is a raster chart, and the
4338 // requested lat/long is within the boundaries of the chart,
4339 // and the VP is not rotated,
4340 // then use the embedded BSB chart georeferencing algorithm
4341 // for greater accuracy
4342 // Additionally, use chart embedded georef if the projection is TMERC
4343 // i.e. NOT MERCATOR and NOT POLYCONIC
4344
4345 // If for some reason the chart rejects the request by returning an error,
4346 // then fall back to Viewport Projection estimate from canvas parameters
4347 if (!g_bopengl && m_singleChart &&
4348 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4349 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4350 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4351 (m_singleChart->GetChartProjectionType() !=
4352 PROJECTION_TRANSVERSE_MERCATOR) &&
4353 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4354 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4355 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4356 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4357 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4358 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4359 // Cur_BSB_Ch->GetCOVRTablenPoints
4360 // ( 0 ), rlon,
4361 // rlat );
4362 // bInside = true;
4363 // if ( bInside )
4364 if (Cur_BSB_Ch) {
4365 // This is a Raster chart....
4366 // If the VP is changing, the raster chart parameters may not yet be
4367 // setup So do that before accessing the chart's embedded
4368 // georeferencing
4369 Cur_BSB_Ch->SetVPRasterParms(vp);
4370 double rpixxd, rpixyd;
4371 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4372 r->m_x = rpixxd;
4373 r->m_y = rpixyd;
4374 return;
4375 }
4376 }
4377 }
4378
4379 // if needed, use the VPoint scaling estimator,
4380 *r = vp.GetDoublePixFromLL(rlat, rlon);
4381}
4382
4383// This routine might be deleted and all of the rendering improved
4384// to have floating point accuracy
4385bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4386 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4387}
4388
4389bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4390 wxPoint *r) {
4391 wxPoint2DDouble p;
4392 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4393
4394 // some projections give nan values when invisible values (other side of
4395 // world) are requested we should stop using integer coordinates or return
4396 // false here (and test it everywhere)
4397 if (std::isnan(p.m_x)) {
4398 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4399 return false;
4400 }
4401
4402 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4403 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4404 else
4405 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4406
4407 return true;
4408}
4409
4410void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4411 double &lon) {
4412 // If the Current Chart is a raster chart, and the
4413 // requested x,y is within the boundaries of the chart,
4414 // and the VP is not rotated,
4415 // then use the embedded BSB chart georeferencing algorithm
4416 // for greater accuracy
4417 // Additionally, use chart embedded georef if the projection is TMERC
4418 // i.e. NOT MERCATOR and NOT POLYCONIC
4419
4420 // If for some reason the chart rejects the request by returning an error,
4421 // then fall back to Viewport Projection estimate from canvas parameters
4422 bool bUseVP = true;
4423
4424 if (!g_bopengl && m_singleChart &&
4425 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4426 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4427 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4428 (m_singleChart->GetChartProjectionType() !=
4429 PROJECTION_TRANSVERSE_MERCATOR) &&
4430 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4431 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4432 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4433 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4434
4435 // TODO maybe need iterative process to validate bInside
4436 // first pass is mercator, then check chart boundaries
4437
4438 if (Cur_BSB_Ch) {
4439 // This is a Raster chart....
4440 // If the VP is changing, the raster chart parameters may not yet be
4441 // setup So do that before accessing the chart's embedded
4442 // georeferencing
4443 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4444
4445 double slat, slon;
4446 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4447 lat = slat;
4448
4449 if (slon < -180.)
4450 slon += 360.;
4451 else if (slon > 180.)
4452 slon -= 360.;
4453
4454 lon = slon;
4455 bUseVP = false;
4456 }
4457 }
4458 }
4459
4460 // if needed, use the VPoint scaling estimator
4461 if (bUseVP) {
4462 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4463 }
4464}
4465
4467 StopMovement();
4468 DoZoomCanvas(factor, false);
4469 extendedSectorLegs.clear();
4470}
4471
4472void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4473 bool stoptimer) {
4474 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4475
4476 if (g_bsmoothpanzoom) {
4477 if (StartTimedMovement(stoptimer)) {
4478 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4479 m_zoom_factor = factor;
4480 }
4481
4482 m_zoom_target = VPoint.chart_scale / factor;
4483 } else {
4484 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4485
4486 DoZoomCanvas(factor, can_zoom_to_cursor);
4487 }
4488
4489 extendedSectorLegs.clear();
4490}
4491
4492void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4493 // possible on startup
4494 if (!ChartData) return;
4495 if (!m_pCurrentStack) return;
4496
4497 /* TODO: queue the quilted loading code to a background thread
4498 so yield is never called from here, and also rendering is not delayed */
4499
4500 // Cannot allow Yield() re-entrancy here
4501 if (m_bzooming) return;
4502 m_bzooming = true;
4503
4504 double old_ppm = GetVP().view_scale_ppm;
4505
4506 // Capture current cursor position for zoom to cursor
4507 double zlat = m_cursor_lat;
4508 double zlon = m_cursor_lon;
4509
4510 double proposed_scale_onscreen =
4511 GetVP().chart_scale /
4512 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4513 bool b_do_zoom = false;
4514
4515 if (factor > 1) {
4516 b_do_zoom = true;
4517
4518 // double zoom_factor = factor;
4519
4520 ChartBase *pc = NULL;
4521
4522 if (!VPoint.b_quilt) {
4523 pc = m_singleChart;
4524 } else {
4525 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4526 if (new_db_index >= 0)
4527 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4528 else { // for whatever reason, no reference chart is known
4529 // Choose the smallest scale chart on the current stack
4530 // and then adjust for scale range
4531 int current_ref_stack_index = -1;
4532 if (m_pCurrentStack->nEntry) {
4533 int trial_index =
4534 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4535 m_pQuilt->SetReferenceChart(trial_index);
4536 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4537 if (new_db_index >= 0)
4538 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4539 }
4540 }
4541
4542 if (m_pCurrentStack)
4543 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4544 new_db_index); // highlite the correct bar entry
4545 }
4546
4547 if (pc) {
4548 // double target_scale_ppm = GetVPScale() * zoom_factor;
4549 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4550 // target_scale_ppm;
4551
4552 // Query the chart to determine the appropriate zoom range
4553 double min_allowed_scale =
4554 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4555
4556 if (proposed_scale_onscreen < min_allowed_scale) {
4557 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4558 m_zoom_factor = 1; /* stop zooming */
4559 b_do_zoom = false;
4560 } else
4561 proposed_scale_onscreen = min_allowed_scale;
4562 }
4563
4564 } else {
4565 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4566 }
4567
4568 } else if (factor < 1) {
4569 b_do_zoom = true;
4570
4571 ChartBase *pc = NULL;
4572
4573 bool b_smallest = false;
4574
4575 if (!VPoint.b_quilt) { // not quilted
4576 pc = m_singleChart;
4577
4578 if (pc) {
4579 // If m_singleChart is not on the screen, unbound the zoomout
4580 LLBBox viewbox = VPoint.GetBBox();
4581 // BoundingBox chart_box;
4582 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4583 double max_allowed_scale;
4584
4585 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4586
4587 // We can allow essentially unbounded zoomout in single chart mode
4588 // if( ChartData->GetDBBoundingBox( current_index,
4589 // &chart_box ) &&
4590 // !viewbox.IntersectOut( chart_box ) )
4591 // // Clamp the minimum scale zoom-out to the value
4592 // specified by the chart max_allowed_scale =
4593 // wxMin(max_allowed_scale, 4.0 *
4594 // pc->GetNormalScaleMax(
4595 // GetCanvasScaleFactor(),
4596 // GetCanvasWidth() ) );
4597 if (proposed_scale_onscreen > max_allowed_scale) {
4598 m_zoom_factor = 1; /* stop zooming */
4599 proposed_scale_onscreen = max_allowed_scale;
4600 }
4601 }
4602
4603 } else {
4604 int new_db_index = m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4605 if (new_db_index >= 0)
4606 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4607
4608 if (m_pCurrentStack)
4609 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4610 new_db_index); // highlite the correct bar entry
4611
4612 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4613
4614 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4615 proposed_scale_onscreen =
4616 wxMin(proposed_scale_onscreen,
4617 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4618 }
4619
4620 // set a minimum scale
4621 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4622 m_absolute_min_scale_ppm)
4623 proposed_scale_onscreen =
4624 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4625 }
4626
4627 double new_scale =
4628 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4629
4630 if (b_do_zoom) {
4631 // Disable ZTC if lookahead is ON, and currently b_follow is active
4632 bool b_allow_ztc = true;
4633 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4634 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4635 if (m_bLookAhead) {
4636 double brg, distance;
4637 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4638 &distance);
4639 dir_to_shift = brg;
4640 meters_to_shift = distance * 1852;
4641 }
4642 // Arrange to combine the zoom and pan into one operation for smoother
4643 // appearance
4644 SetVPScale(new_scale, false); // adjust, but deferred refresh
4645 wxPoint r;
4646 GetCanvasPointPix(zlat, zlon, &r);
4647 // this will emit the Refresh()
4648 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4649 } else {
4650 SetVPScale(new_scale);
4651 if (m_bFollow) DoCanvasUpdate();
4652 }
4653 }
4654
4655 m_bzooming = false;
4656}
4657
4658void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4659 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4660 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4661}
4662
4663int rot;
4664void ChartCanvas::RotateCanvas(double dir) {
4665 // SetUpMode(NORTH_UP_MODE);
4666
4667 if (g_bsmoothpanzoom) {
4668 if (StartTimedMovement()) {
4669 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4670 m_rotation_speed = dir * 60;
4671 }
4672 } else {
4673 double speed = dir * 10;
4674 if (m_modkeys == wxMOD_ALT) speed /= 20;
4675 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4676 }
4677}
4678
4679void ChartCanvas::DoRotateCanvas(double rotation) {
4680 while (rotation < 0) rotation += 2 * PI;
4681 while (rotation > 2 * PI) rotation -= 2 * PI;
4682
4683 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4684
4685 SetVPRotation(rotation);
4686 parent_frame->UpdateRotationState(VPoint.rotation);
4687}
4688
4689void ChartCanvas::DoTiltCanvas(double tilt) {
4690 while (tilt < 0) tilt = 0;
4691 while (tilt > .95) tilt = .95;
4692
4693 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4694
4695 VPoint.tilt = tilt;
4696 Refresh(false);
4697}
4698
4699void ChartCanvas::TogglebFollow(void) {
4700 if (!m_bFollow)
4701 SetbFollow();
4702 else
4703 ClearbFollow();
4704}
4705
4706void ChartCanvas::ClearbFollow(void) {
4707 m_bFollow = false; // update the follow flag
4708
4709 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4710
4711 UpdateFollowButtonState();
4712
4713 DoCanvasUpdate();
4714 ReloadVP();
4715 parent_frame->SetChartUpdatePeriod();
4716}
4717
4718void ChartCanvas::SetbFollow(void) {
4719 // Is the OWNSHIP on-screen?
4720 // If not, then reset the OWNSHIP offset to 0 (center screen)
4721 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4722 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4723 m_OSoffsetx = 0;
4724 m_OSoffsety = 0;
4725 }
4726
4727 // Apply the present b_follow offset values to ship position
4728 wxPoint2DDouble p;
4729 GetDoubleCanvasPointPix(gLat, gLon, &p);
4730 p.m_x += m_OSoffsetx;
4731 p.m_y -= m_OSoffsety;
4732
4733 // compute the target center screen lat/lon
4734 double dlat, dlon;
4735 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4736
4737 JumpToPosition(dlat, dlon, GetVPScale());
4738 m_bFollow = true;
4739
4740 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4741 UpdateFollowButtonState();
4742
4743 if (!g_bSmoothRecenter) {
4744 DoCanvasUpdate();
4745 ReloadVP();
4746 }
4747 parent_frame->SetChartUpdatePeriod();
4748}
4749
4750void ChartCanvas::UpdateFollowButtonState(void) {
4751 if (m_muiBar) {
4752 if (!m_bFollow)
4753 m_muiBar->SetFollowButtonState(0);
4754 else {
4755 if (m_bLookAhead)
4756 m_muiBar->SetFollowButtonState(2);
4757 else
4758 m_muiBar->SetFollowButtonState(1);
4759 }
4760 }
4761
4762#ifdef __ANDROID__
4763 if (!m_bFollow)
4764 androidSetFollowTool(0);
4765 else {
4766 if (m_bLookAhead)
4767 androidSetFollowTool(2);
4768 else
4769 androidSetFollowTool(1);
4770 }
4771#endif
4772
4773 // Look for plugin using API-121 or later
4774 // If found, make the follow state callback.
4775 if (g_pi_manager) {
4776 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4777 if (pic->m_enabled && pic->m_init_state) {
4778 switch (pic->m_api_version) {
4779 case 121: {
4780 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4781 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4782 break;
4783 }
4784 default:
4785 break;
4786 }
4787 }
4788 }
4789 }
4790}
4791
4792void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4793 if (g_bSmoothRecenter && !m_routeState) {
4794 if (StartSmoothJump(lat, lon, scale_ppm))
4795 return;
4796 else {
4797 // move closer to the target destination, and try again
4798 double gcDist, gcBearingEnd;
4799 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4800 &gcBearingEnd);
4801 gcBearingEnd += 180;
4802 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4803 GetCanvasWidth() / GetVPScale(); // meters
4804 double lon_offset =
4805 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4806 double new_lat = lat + (lat_offset / (1852 * 60));
4807 double new_lon = lon + (lon_offset / (1852 * 60));
4808 SetViewPoint(new_lat, new_lon);
4809 ReloadVP();
4810 StartSmoothJump(lat, lon, scale_ppm);
4811 return;
4812 }
4813 }
4814
4815 if (lon > 180.0) lon -= 360.0;
4816 m_vLat = lat;
4817 m_vLon = lon;
4818 StopMovement();
4819 m_bFollow = false;
4820
4821 if (!GetQuiltMode()) {
4822 double skew = 0;
4823 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4824 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4825 } else {
4826 if (scale_ppm != GetVPScale()) {
4827 // XXX should be done in SetViewPoint
4828 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4829 AdjustQuiltRefChart();
4830 }
4831 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4832 }
4833
4834 ReloadVP();
4835
4836 UpdateFollowButtonState();
4837
4838 // TODO
4839 // if( g_pi_manager ) {
4840 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4841 // }
4842}
4843
4844bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
4845 // Check distance to jump, in pixels at current chart scale
4846 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
4847 // width.
4848 double gcDist;
4849 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
4850 double distance_pixels = gcDist * GetVPScale();
4851 if (distance_pixels > 0.5 * GetCanvasWidth()) {
4852 // Jump is too far, try again
4853 return false;
4854 }
4855
4856 // Save where we're coming from
4857 m_startLat = m_vLat;
4858 m_startLon = m_vLon;
4859 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
4860
4861 // Save where we want to end up
4862 m_endLat = lat;
4863 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
4864 m_endScale = scale_ppm;
4865
4866 // Setup timing
4867 m_animationDuration = 600; // ms
4868 m_animationStart = wxGetLocalTimeMillis();
4869
4870 // Stop any previous movement, ensure no conflicts
4871 StopMovement();
4872 m_bFollow = false;
4873
4874 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
4875 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
4876 m_animationActive = true;
4877
4878 return true;
4879}
4880
4881void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
4882 // Calculate time fraction from 0..1
4883 wxLongLong now = wxGetLocalTimeMillis();
4884 double elapsed = (now - m_animationStart).ToDouble();
4885 double t = elapsed / m_animationDuration.ToDouble();
4886 if (t > 1.0) t = 1.0;
4887
4888 // Ease function for smoother movement
4889 double e = easeOutCubic(t);
4890
4891 // Interpolate lat/lon/scale
4892 double curLat = m_startLat + (m_endLat - m_startLat) * e;
4893 double curLon = m_startLon + (m_endLon - m_startLon) * e;
4894 double curScale = m_startScale + (m_endScale - m_startScale) * e;
4895
4896 // Update viewpoint
4897 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
4898 // portion)
4899 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
4900 ReloadVP();
4901
4902 // If we reached the end, stop the timer and finalize
4903 if (t >= 1.0) {
4904 m_easeTimer.Stop();
4905 m_animationActive = false;
4906 UpdateFollowButtonState();
4907 ZoomCanvasSimple(1.0001);
4908 DoCanvasUpdate();
4909 ReloadVP();
4910 }
4911}
4912
4913bool ChartCanvas::PanCanvas(double dx, double dy) {
4914 if (!ChartData) return false;
4915
4916 extendedSectorLegs.clear();
4917
4918 double dlat, dlon;
4919 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
4920
4921 int iters = 0;
4922 for (;;) {
4923 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
4924
4925 if (iters++ > 5) return false;
4926 if (!std::isnan(dlat)) break;
4927
4928 dx *= .5, dy *= .5;
4929 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
4930 }
4931
4932 // avoid overshooting the poles
4933 if (dlat > 90)
4934 dlat = 90;
4935 else if (dlat < -90)
4936 dlat = -90;
4937
4938 if (dlon > 360.) dlon -= 360.;
4939 if (dlon < -360.) dlon += 360.;
4940
4941 // This should not really be necessary, but round-trip georef on some
4942 // charts is not perfect, So we can get creep on repeated unidimensional
4943 // pans, and corrupt chart cacheing.......
4944
4945 // But this only works on north-up projections
4946 // TODO: can we remove this now?
4947 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
4948 // .001 ) ) {
4949 //
4950 // if( dx == 0 ) dlon = clon;
4951 // if( dy == 0 ) dlat = clat;
4952 // }
4953
4954 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
4955
4956 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
4957
4958 if (VPoint.b_quilt) {
4959 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
4960 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
4961 // Tweak the scale slightly for a new ref chart
4962 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
4963 if (pc) {
4964 double tweak_scale_ppm =
4965 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
4966 SetVPScale(tweak_scale_ppm);
4967 }
4968 }
4969
4970 if (new_ref_dbIndex == -1) {
4971#pragma GCC diagnostic push
4972#pragma GCC diagnostic ignored "-Warray-bounds"
4973 // The compiler sees a -1 index being used. Does not happen, though.
4974
4975 // for whatever reason, no reference chart is known
4976 // Probably panned out of the coverage region
4977 // If any charts are anywhere on-screen, choose the smallest
4978 // scale chart on the screen to be a new reference chart.
4979 int trial_index = -1;
4980 if (m_pCurrentStack->nEntry) {
4981 int trial_index =
4982 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4983 }
4984
4985 if (trial_index < 0) {
4986 auto full_screen_array = GetQuiltFullScreendbIndexArray();
4987 if (full_screen_array.size())
4988 trial_index = full_screen_array[full_screen_array.size() - 1];
4989 }
4990
4991 if (trial_index >= 0) {
4992 m_pQuilt->SetReferenceChart(trial_index);
4993 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
4994 VPoint.rotation);
4995 ReloadVP();
4996 }
4997#pragma GCC diagnostic pop
4998 }
4999 }
5000
5001 // Turn off bFollow only if the ownship has left the screen
5002 if (m_bFollow) {
5003 double offx, offy;
5004 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5005
5006 double offset_angle = atan2(offy, offx);
5007 double offset_distance = sqrt((offy * offy) + (offx * offx));
5008 double chart_angle = GetVPRotation();
5009 double target_angle = chart_angle - offset_angle;
5010 double d_east_mod = offset_distance * cos(target_angle);
5011 double d_north_mod = offset_distance * sin(target_angle);
5012
5013 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5014 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5015
5016 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5017 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5018 m_bFollow = false; // update the follow flag
5019 UpdateFollowButtonState();
5020 }
5021 }
5022
5023 Refresh(false);
5024
5025 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5026
5027 return true;
5028}
5029
5030bool ChartCanvas::IsOwnshipOnScreen() {
5031 wxPoint r;
5032 GetCanvasPointPix(gLat, gLon, &r);
5033 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5034 ((r.y > 0) && r.y < GetCanvasHeight()))
5035 return true;
5036 else
5037 return false;
5038}
5039
5040void ChartCanvas::ReloadVP(bool b_adjust) {
5041 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5042
5043 LoadVP(VPoint, b_adjust);
5044}
5045
5046void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5047#ifdef ocpnUSE_GL
5048 if (g_bopengl && m_glcc) {
5049 m_glcc->Invalidate();
5050 if (m_glcc->GetSize() != GetSize()) {
5051 m_glcc->SetSize(GetSize());
5052 }
5053 } else
5054#endif
5055 {
5056 m_cache_vp.Invalidate();
5057 m_bm_cache_vp.Invalidate();
5058 }
5059
5060 VPoint.Invalidate();
5061
5062 if (m_pQuilt) m_pQuilt->Invalidate();
5063
5064 // Make sure that the Selected Group is sensible...
5065 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5066 // m_groupIndex = 0;
5067 // if( !CheckGroup( m_groupIndex ) )
5068 // m_groupIndex = 0;
5069
5070 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5071 vp.m_projection_type, b_adjust);
5072}
5073
5074void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5075 m_pQuilt->SetReferenceChart(dbIndex);
5076 VPoint.Invalidate();
5077 m_pQuilt->Invalidate();
5078}
5079
5080double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5081 if (m_pQuilt)
5082 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5083 else
5084 return vp.view_scale_ppm;
5085}
5086
5087// Verify and adjust the current reference chart,
5088// so that it will not lead to excessive overzoom or underzoom onscreen
5089int ChartCanvas::AdjustQuiltRefChart() {
5090 int ret = -1;
5091 if (m_pQuilt) {
5092 wxASSERT(ChartData);
5093 ChartBase *pc =
5094 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5095 if (pc) {
5096 double min_ref_scale =
5097 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5098 double max_ref_scale =
5099 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5100
5101 if (VPoint.chart_scale < min_ref_scale) {
5102 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5103 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5104 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5105 } else {
5106 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5107
5108 if (!brender_ok) {
5109 int target_stack_index = wxNOT_FOUND;
5110 int il = 0;
5111 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5112 if (index == m_pQuilt->GetRefChartdbIndex()) {
5113 target_stack_index = il;
5114 break;
5115 }
5116 il++;
5117 }
5118 if (wxNOT_FOUND == target_stack_index) // should never happen...
5119 target_stack_index = 0;
5120
5121 int ref_family = pc->GetChartFamily();
5122 int extended_array_count =
5123 m_pQuilt->GetExtendedStackIndexArray().size();
5124 while ((!brender_ok) &&
5125 ((int)target_stack_index < (extended_array_count - 1))) {
5126 target_stack_index++;
5127 int test_db_index =
5128 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5129
5130 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5131 IsChartQuiltableRef(test_db_index)) {
5132 // open the target, and check the min_scale
5133 ChartBase *ptest_chart =
5134 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5135 if (ptest_chart) {
5136 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5137 }
5138 }
5139 }
5140
5141 if (brender_ok) { // found a better reference chart
5142 int new_db_index =
5143 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5144 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5145 IsChartQuiltableRef(new_db_index)) {
5146 m_pQuilt->SetReferenceChart(new_db_index);
5147 ret = new_db_index;
5148 } else
5149 ret = m_pQuilt->GetRefChartdbIndex();
5150 } else
5151 ret = m_pQuilt->GetRefChartdbIndex();
5152
5153 } else
5154 ret = m_pQuilt->GetRefChartdbIndex();
5155 }
5156 } else
5157 ret = -1;
5158 }
5159
5160 return ret;
5161}
5162
5163void ChartCanvas::UpdateCanvasOnGroupChange(void) {
5164 delete m_pCurrentStack;
5165 m_pCurrentStack = new ChartStack;
5166 wxASSERT(ChartData);
5167 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5168 m_groupIndex);
5169
5170 if (m_pQuilt) {
5171 m_pQuilt->Compose(VPoint);
5172 SetFocus();
5173 }
5174}
5175
5176bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5177 double latNE, double lonNE) {
5178 // Center Point
5179 double latc = (latSW + latNE) / 2.0;
5180 double lonc = (lonSW + lonNE) / 2.0;
5181
5182 // Get scale in ppm (latitude)
5183 double ne_easting, ne_northing;
5184 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5185
5186 double sw_easting, sw_northing;
5187 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5188
5189 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5190
5191 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5192}
5193
5194bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5195 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5196 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5197}
5198
5199bool ChartCanvas::SetVPProjection(int projection) {
5200 if (!g_bopengl) // alternative projections require opengl
5201 return false;
5202
5203 // the view scale varies depending on geographic location and projection
5204 // rescale to keep the relative scale on the screen the same
5205 double prev_true_scale_ppm = m_true_scale_ppm;
5206 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5207 VPoint.skew, VPoint.rotation, projection) &&
5208 SetVPScale(wxMax(
5209 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5210 m_absolute_min_scale_ppm));
5211}
5212
5213bool ChartCanvas::SetViewPoint(double lat, double lon) {
5214 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5215 VPoint.rotation);
5216}
5217
5218bool ChartCanvas::SetVPRotation(double angle) {
5219 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5220 VPoint.skew, angle);
5221}
5222bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5223 double skew, double rotation, int projection,
5224 bool b_adjust, bool b_refresh) {
5225 bool b_ret = false;
5226 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5227 skew -= 2 * PI;
5228 // Any sensible change?
5229 if (VPoint.IsValid()) {
5230 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5231 (fabs(VPoint.skew - skew) < 1e-9) &&
5232 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5233 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5234 (VPoint.m_projection_type == projection ||
5235 projection == PROJECTION_UNKNOWN))
5236 return false;
5237 }
5238 if (VPoint.m_projection_type != projection)
5239 VPoint.InvalidateTransformCache(); // invalidate
5240
5241 // Take a local copy of the last viewport
5242 ViewPort last_vp = VPoint;
5243
5244 VPoint.skew = skew;
5245 VPoint.clat = lat;
5246 VPoint.clon = lon;
5247 VPoint.rotation = rotation;
5248 VPoint.view_scale_ppm = scale_ppm;
5249 if (projection != PROJECTION_UNKNOWN)
5250 VPoint.SetProjectionType(projection);
5251 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5252 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5253
5254 // don't allow latitude above 88 for mercator (90 is infinity)
5255 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5256 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5257 if (VPoint.clat > 89.5)
5258 VPoint.clat = 89.5;
5259 else if (VPoint.clat < -89.5)
5260 VPoint.clat = -89.5;
5261 }
5262
5263 // don't zoom out too far for transverse mercator polyconic until we resolve
5264 // issues
5265 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5266 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5267 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5268
5269 // SetVPRotation(rotation);
5270
5271 if (!g_bopengl) // tilt is not possible without opengl
5272 VPoint.tilt = 0;
5273
5274 if ((VPoint.pix_width <= 0) ||
5275 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5276 return false;
5277
5278 bool bwasValid = VPoint.IsValid();
5279 VPoint.Validate(); // Mark this ViewPoint as OK
5280
5281 // Has the Viewport scale changed? If so, invalidate the vp
5282 if (last_vp.view_scale_ppm != scale_ppm) {
5283 m_cache_vp.Invalidate();
5284 InvalidateGL();
5285 }
5286
5287 // A preliminary value, may be tweaked below
5288 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5289
5290 // recompute cursor position
5291 // and send to interested plugins if the mouse is actually in this window
5292 int mouseX = mouse_x;
5293 int mouseY = mouse_y;
5294 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5295 (mouseY < VPoint.pix_height)) {
5296 double lat, lon;
5297 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5298 m_cursor_lat = lat;
5299 m_cursor_lon = lon;
5300 SendCursorLatLonToAllPlugIns(lat, lon);
5301 }
5302
5303 if (!VPoint.b_quilt && m_singleChart) {
5304 VPoint.SetBoxes();
5305
5306 // Allow the chart to adjust the new ViewPort for performance optimization
5307 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5308 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5309
5310 // If there is a sensible change in the chart render, refresh the whole
5311 // screen
5312 if ((!m_cache_vp.IsValid()) ||
5313 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5314 Refresh(false);
5315 b_ret = true;
5316 } else {
5317 wxPoint cp_last, cp_this;
5318 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5319 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5320
5321 if (cp_last != cp_this) {
5322 Refresh(false);
5323 b_ret = true;
5324 }
5325 }
5326 // Create the stack
5327 if (m_pCurrentStack) {
5328 assert(ChartData != 0);
5329 int current_db_index;
5330 current_db_index =
5331 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5332
5333 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5334 m_groupIndex);
5335 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5336 }
5337
5338 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5339 }
5340
5341 // Handle the quilted case
5342 if (VPoint.b_quilt) {
5343 if (last_vp.view_scale_ppm != scale_ppm)
5344 m_pQuilt->InvalidateAllQuiltPatchs();
5345
5346 // Create the quilt
5347 if (ChartData /*&& ChartData->IsValid()*/) {
5348 if (!m_pCurrentStack) return false;
5349
5350 int current_db_index;
5351 current_db_index =
5352 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5353
5354 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5355 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5356
5357 // Check to see if the current quilt reference chart is in the new stack
5358 int current_ref_stack_index = -1;
5359 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5360 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5361 current_ref_stack_index = i;
5362 }
5363
5364 if (g_bFullScreenQuilt) {
5365 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5366 }
5367
5368 // We might need a new Reference Chart
5369 bool b_needNewRef = false;
5370
5371 // If the new stack does not contain the current ref chart....
5372 if ((-1 == current_ref_stack_index) &&
5373 (m_pQuilt->GetRefChartdbIndex() >= 0))
5374 b_needNewRef = true;
5375
5376 // Would the current Ref Chart be excessively underzoomed?
5377 // We need to check this here to be sure, since we cannot know where the
5378 // reference chart was assigned. For instance, the reference chart may
5379 // have been selected from the config file, or from a long jump with a
5380 // chart family switch implicit. Anyway, we check to be sure....
5381 bool renderable = true;
5382 ChartBase *referenceChart =
5383 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5384 if (referenceChart) {
5385 double chartMaxScale = referenceChart->GetNormalScaleMax(
5386 GetCanvasScaleFactor(), GetCanvasWidth());
5387 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5388 }
5389 if (!renderable) b_needNewRef = true;
5390
5391 // Need new refchart?
5392 if (b_needNewRef) {
5393 const ChartTableEntry &cte_ref =
5394 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5395 int target_scale = cte_ref.GetScale();
5396 int target_type = cte_ref.GetChartType();
5397 int candidate_stack_index;
5398
5399 // reset the ref chart in a way that does not lead to excessive
5400 // underzoom, for performance reasons Try to find a chart that is the
5401 // same type, and has a scale of just smaller than the current ref
5402 // chart
5403
5404 candidate_stack_index = 0;
5405 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5406 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5407 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5408 int candidate_scale = cte_candidate.GetScale();
5409 int candidate_type = cte_candidate.GetChartType();
5410
5411 if ((candidate_scale >= target_scale) &&
5412 (candidate_type == target_type)) {
5413 bool renderable = true;
5414 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5415 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5416 if (tentative_referenceChart) {
5417 double chartMaxScale =
5418 tentative_referenceChart->GetNormalScaleMax(
5419 GetCanvasScaleFactor(), GetCanvasWidth());
5420 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5421 }
5422
5423 if (renderable) break;
5424 }
5425
5426 candidate_stack_index++;
5427 }
5428
5429 // If that did not work, look for a chart of just larger scale and
5430 // same type
5431 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5432 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5433 while (candidate_stack_index >= 0) {
5434 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5435 if (idx >= 0) {
5436 const ChartTableEntry &cte_candidate =
5437 ChartData->GetChartTableEntry(idx);
5438 int candidate_scale = cte_candidate.GetScale();
5439 int candidate_type = cte_candidate.GetChartType();
5440
5441 if ((candidate_scale <= target_scale) &&
5442 (candidate_type == target_type))
5443 break;
5444 }
5445 candidate_stack_index--;
5446 }
5447 }
5448
5449 // and if that did not work, chose stack entry 0
5450 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5451 (candidate_stack_index < 0))
5452 candidate_stack_index = 0;
5453
5454 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5455
5456 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5457 }
5458
5459 if (!g_bopengl) {
5460 // Preset the VPoint projection type to match what the quilt projection
5461 // type will be
5462 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5463
5464 // Always keep the default Mercator projection if the reference chart is
5465 // not in the PatchList or the scale is too small for it to render.
5466
5467 bool renderable = true;
5468 ChartBase *referenceChart =
5469 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5470 if (referenceChart) {
5471 double chartMaxScale = referenceChart->GetNormalScaleMax(
5472 GetCanvasScaleFactor(), GetCanvasWidth());
5473 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5474 proj = ChartData->GetDBChartProj(ref_db_index);
5475 } else
5476 proj = PROJECTION_MERCATOR;
5477
5478 VPoint.b_MercatorProjectionOverride =
5479 (m_pQuilt->GetnCharts() == 0 || !renderable);
5480
5481 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5482
5483 VPoint.SetProjectionType(proj);
5484 }
5485
5486 VPoint.SetBoxes();
5487
5488 // If this quilt will be a perceptible delta from the existing quilt,
5489 // then refresh the entire screen
5490 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5491 // Allow the quilt to adjust the new ViewPort for performance
5492 // optimization This will normally be only a fractional (i.e.
5493 // sub-pixel) adjustment...
5494 if (b_adjust) {
5495 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5496 }
5497
5498 // ChartData->ClearCacheInUseFlags();
5499 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5500
5501 // wxStopWatch sw;
5502
5503#ifdef __ANDROID__
5504 // This is an optimization for panning on touch screen systems.
5505 // The quilt composition is deferred until the OnPaint() message gets
5506 // finally removed and processed from the message queue.
5507 // Takes advantage of the fact that touch-screen pan gestures are
5508 // usually short in distance,
5509 // so not requiring a full quilt rebuild until the pan gesture is
5510 // complete.
5511 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5512 // qDebug() << "Force compose";
5513 m_pQuilt->Compose(VPoint);
5514 } else {
5515 m_pQuilt->Invalidate();
5516 }
5517#else
5518 m_pQuilt->Compose(VPoint);
5519#endif
5520
5521 // printf("comp time %ld\n", sw.Time());
5522
5523 // If the extended chart stack has changed, invalidate any cached
5524 // render bitmap
5525 // if(m_pQuilt->GetXStackHash() != hash1) {
5526 // m_bm_cache_vp.Invalidate();
5527 // InvalidateGL();
5528 // }
5529
5530 ChartData->PurgeCacheUnusedCharts(0.7);
5531
5532 if (b_refresh) Refresh(false);
5533
5534 b_ret = true;
5535 }
5536 }
5537
5538 VPoint.skew = 0.; // Quilting supports 0 Skew
5539 } else if (!g_bopengl) {
5540 OcpnProjType projection = PROJECTION_UNKNOWN;
5541 if (m_singleChart) // viewport projection must match chart projection
5542 // without opengl
5543 projection = m_singleChart->GetChartProjectionType();
5544 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5545 VPoint.SetProjectionType(projection);
5546 }
5547
5548 // Has the Viewport projection changed? If so, invalidate the vp
5549 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5550 m_cache_vp.Invalidate();
5551 InvalidateGL();
5552 }
5553
5554 UpdateCanvasControlBar(); // Refresh the Piano
5555
5556 VPoint.chart_scale = 1.0; // fallback default value
5557
5558 /*if (!VPoint.GetBBox().GetValid())*/ VPoint.SetBoxes();
5559
5560 if (VPoint.GetBBox().GetValid()) {
5561 // Update the viewpoint reference scale
5562 if (m_singleChart)
5563 VPoint.ref_scale = m_singleChart->GetNativeScale();
5564 else {
5565#ifdef __ANDROID__
5566 // This is an optimization for panning on touch screen systems.
5567 // See above.
5568 // Quilt might not be fully composed at this point, so for cm93
5569 // the reference scale may not be known.
5570 // In this case, do not update the VP ref_scale.
5571 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5572 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5573 }
5574#else
5575 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5576#endif
5577 }
5578
5579 // Calculate the on-screen displayed actual scale
5580 // by a simple traverse northward from the center point
5581 // of roughly one eighth of the canvas height
5582 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5583
5584 double delta_check =
5585 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5586 delta_check /= 8.;
5587
5588 double check_point = wxMin(89., VPoint.clat);
5589
5590 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5591
5592 double rhumbDist;
5593 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5594 VPoint.clon, 0, &rhumbDist);
5595
5596 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5597 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5598 // Calculate the distance between r1 and r in physical pixels.
5599 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5600 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5601
5602 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5603
5604 // A fall back in case of very high zoom-out, giving delta_y == 0
5605 // which can probably only happen with vector charts
5606 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5607
5608 // Another fallback, for highly zoomed out charts
5609 // This adjustment makes the displayed TrueScale correspond to the
5610 // same algorithm used to calculate the chart zoom-out limit for
5611 // ChartDummy.
5612 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5613
5614 if (m_true_scale_ppm)
5615 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5616 else
5617 VPoint.chart_scale = 1.0;
5618
5619 // Create a nice renderable string
5620 double round_factor = 1000.;
5621 if (VPoint.chart_scale <= 1000.)
5622 round_factor = 10.;
5623 else if (VPoint.chart_scale <= 10000.)
5624 round_factor = 100.;
5625 else if (VPoint.chart_scale <= 100000.)
5626 round_factor = 1000.;
5627
5628 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5629 double retina_coef = 1;
5630#ifdef ocpnUSE_GL
5631#ifdef __WXOSX__
5632 if (g_bopengl) {
5633 retina_coef = GetContentScaleFactor();
5634 }
5635#endif
5636#endif
5637
5638 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5639 // rounded to the nearest 10, 100 or 1000.
5640 //
5641 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5642 // true_scale_display. That does not make sense. The chart scale should be
5643 // the same as the true scale within the limits of the rounding factor.
5644 double true_scale_display =
5645 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5646 wxString text;
5647
5648 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5649
5650 if (m_displayed_scale_factor > 10.0)
5651 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5652 m_displayed_scale_factor);
5653 else if (m_displayed_scale_factor > 1.0)
5654 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5655 m_displayed_scale_factor);
5656 else if (m_displayed_scale_factor > 0.1) {
5657 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5658 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5659 } else if (m_displayed_scale_factor > 0.01) {
5660 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5661 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5662 } else {
5663 text.Printf(
5664 "%s %4.0f (---)", _("Scale"),
5665 true_scale_display); // Generally, no chart, so no chart scale factor
5666 }
5667
5668 m_scaleValue = true_scale_display;
5669 m_scaleText = text;
5670 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5671
5672 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5673 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5674 // Check to see if the text will fit in the StatusBar field...
5675 bool b_noshow = false;
5676 {
5677 int w = 0;
5678 int h;
5679 wxClientDC dc(parent_frame->GetStatusBar());
5680 if (dc.IsOk()) {
5681 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5682 dc.SetFont(*templateFont);
5683 dc.GetTextExtent(text, &w, &h);
5684
5685 // If text is too long for the allocated field, try to reduce the text
5686 // string a bit.
5687 wxRect rect;
5688 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5689 if (w && w > rect.width) {
5690 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5691 }
5692
5693 // Test again...if too big still, then give it up.
5694 dc.GetTextExtent(text, &w, &h);
5695
5696 if (w && w > rect.width) {
5697 b_noshow = true;
5698 }
5699 }
5700 }
5701
5702 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5703 }
5704 }
5705
5706 // Maintain member vLat/vLon
5707 m_vLat = VPoint.clat;
5708 m_vLon = VPoint.clon;
5709
5710 return b_ret;
5711}
5712
5713// Static Icon definitions for some symbols requiring
5714// scaling/rotation/translation Very specific wxDC draw commands are
5715// necessary to properly render these icons...See the code in
5716// ShipDraw()
5717
5718// This icon was adapted and scaled from the S52 Presentation Library
5719// version 3_03.
5720// Symbol VECGND02
5721
5722static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5723
5724// This ownship icon was adapted and scaled from the S52 Presentation
5725// Library version 3_03 Symbol OWNSHP05
5726static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5727 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5728
5729wxColour ChartCanvas::PredColor() {
5730 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5731 // visibility.
5732 if (SHIP_NORMAL == m_ownship_state)
5733 return GetGlobalColor("URED");
5734
5735 else if (SHIP_LOWACCURACY == m_ownship_state)
5736 return GetGlobalColor("YELO1");
5737
5738 return GetGlobalColor("NODTA");
5739}
5740
5741wxColour ChartCanvas::ShipColor() {
5742 // Establish ship color
5743 // It changes color based on GPS and Chart accuracy/availability
5744
5745 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5746
5747 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5748
5749 return GetGlobalColor("URED"); // default is OK
5750}
5751
5752void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5753 wxPoint2DDouble lShipMidPoint) {
5754 dc.SetPen(wxPen(PredColor(), 2));
5755
5756 if (SHIP_NORMAL == m_ownship_state)
5757 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5758 else
5759 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5760
5761 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5762 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5763
5764 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5765 lShipMidPoint.m_y);
5766 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5767 lShipMidPoint.m_y + 12);
5768}
5769
5770void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5771 wxPoint GPSOffsetPixels,
5772 wxPoint2DDouble lGPSPoint) {
5773 // if (m_animationActive) return;
5774 // Develop a uniform length for course predictor line dash length, based on
5775 // physical display size Use this reference length to size all other graphics
5776 // elements
5777 float ref_dim = m_display_size_mm / 24;
5778 ref_dim = wxMin(ref_dim, 12);
5779 ref_dim = wxMax(ref_dim, 6);
5780
5781 wxColour cPred;
5782 cPred.Set(g_cog_predictor_color);
5783 if (cPred == wxNullColour) cPred = PredColor();
5784
5785 // Establish some graphic element line widths dependent on the platform
5786 // display resolution
5787 // double nominal_line_width_pix = wxMax(1.0,
5788 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5789 // not less than 1 pixel
5790 double nominal_line_width_pix = wxMax(
5791 1.0,
5792 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5793
5794 // If the calculated value is greater than the config file spec value, then
5795 // use it.
5796 if (nominal_line_width_pix > g_cog_predictor_width)
5797 g_cog_predictor_width = nominal_line_width_pix;
5798
5799 // Calculate ownship Position Predictor
5800 wxPoint lPredPoint, lHeadPoint;
5801
5802 float pCog = std::isnan(gCog) ? 0 : gCog;
5803 float pSog = std::isnan(gSog) ? 0 : gSog;
5804
5805 double pred_lat, pred_lon;
5806 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5807 &pred_lat, &pred_lon);
5808 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5809
5810 // test to catch the case where COG/HDG line crosses the screen
5811 LLBBox box;
5812
5813 // Should we draw the Head vector?
5814 // Compare the points lHeadPoint and lPredPoint
5815 // If they differ by more than n pixels, and the head vector is valid, then
5816 // render the head vector
5817
5818 float ndelta_pix = 10.;
5819 double hdg_pred_lat, hdg_pred_lon;
5820 bool b_render_hdt = false;
5821 if (!std::isnan(gHdt)) {
5822 // Calculate ownship Heading pointer as a predictor
5823 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5824 &hdg_pred_lon);
5825 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5826 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5827 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5828 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5829 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5830 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5831 }
5832 }
5833
5834 // draw course over ground if they are longer than the ship
5835 wxPoint lShipMidPoint;
5836 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5837 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5838 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5839 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5840
5841 if (lpp >= img_height / 2) {
5842 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5843 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5844 !std::isnan(gSog)) {
5845 // COG Predictor
5846 float dash_length = ref_dim;
5847 wxDash dash_long[2];
5848 dash_long[0] =
5849 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
5850 g_cog_predictor_width); // Long dash , in mm <---------+
5851 dash_long[1] = dash_long[0] / 2.0; // Short gap
5852
5853 // On ultra-hi-res displays, do not allow the dashes to be greater than
5854 // 250, since it is defined as (char)
5855 if (dash_length > 250.) {
5856 dash_long[0] = 250. / g_cog_predictor_width;
5857 dash_long[1] = dash_long[0] / 2;
5858 }
5859
5860 wxPen ppPen2(cPred, g_cog_predictor_width,
5861 (wxPenStyle)g_cog_predictor_style);
5862 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
5863 ppPen2.SetDashes(2, dash_long);
5864 dc.SetPen(ppPen2);
5865 dc.StrokeLine(
5866 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
5867 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
5868
5869 if (g_cog_predictor_width > 1) {
5870 float line_width = g_cog_predictor_width / 3.;
5871
5872 wxDash dash_long3[2];
5873 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
5874 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
5875
5876 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
5877 (wxPenStyle)g_cog_predictor_style);
5878 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
5879 ppPen3.SetDashes(2, dash_long3);
5880 dc.SetPen(ppPen3);
5881 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
5882 lGPSPoint.m_y + GPSOffsetPixels.y,
5883 lPredPoint.x + GPSOffsetPixels.x,
5884 lPredPoint.y + GPSOffsetPixels.y);
5885 }
5886
5887 if (g_cog_predictor_endmarker) {
5888 // Prepare COG predictor endpoint icon
5889 double png_pred_icon_scale_factor = .4;
5890 if (g_ShipScaleFactorExp > 1.0)
5891 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
5892 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
5893
5894 wxPoint icon[4];
5895
5896 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
5897 (float)(lPredPoint.x - lShipMidPoint.x));
5898 cog_rad += (float)PI;
5899
5900 for (int i = 0; i < 4; i++) {
5901 int j = i * 2;
5902 double pxa = (double)(s_png_pred_icon[j]);
5903 double pya = (double)(s_png_pred_icon[j + 1]);
5904
5905 pya *= png_pred_icon_scale_factor;
5906 pxa *= png_pred_icon_scale_factor;
5907
5908 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
5909 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
5910
5911 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
5912 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
5913 }
5914
5915 // Render COG endpoint icon
5916 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
5917 wxPENSTYLE_SOLID);
5918 dc.SetPen(ppPen1);
5919 dc.SetBrush(wxBrush(cPred));
5920
5921 dc.StrokePolygon(4, icon);
5922 }
5923 }
5924 }
5925
5926 // HDT Predictor
5927 if (b_render_hdt) {
5928 float hdt_dash_length = ref_dim * 0.4;
5929
5930 cPred.Set(g_ownship_HDTpredictor_color);
5931 if (cPred == wxNullColour) cPred = PredColor();
5932 float hdt_width =
5933 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
5934 : g_cog_predictor_width * 0.8);
5935 wxDash dash_short[2];
5936 dash_short[0] =
5937 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
5938 hdt_width); // Short dash , in mm <---------+
5939 dash_short[1] =
5940 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
5941 hdt_width); // Short gap |
5942
5943 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
5944 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
5945 ppPen2.SetDashes(2, dash_short);
5946
5947 dc.SetPen(ppPen2);
5948 dc.StrokeLine(
5949 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
5950 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
5951
5952 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
5953 dc.SetPen(ppPen1);
5954 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
5955
5956 if (g_ownship_HDTpredictor_endmarker) {
5957 double nominal_circle_size_pixels = wxMax(
5958 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
5959
5960 // Scale the circle to ChartScaleFactor, slightly softened....
5961 if (g_ShipScaleFactorExp > 1.0)
5962 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
5963
5964 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
5965 lHeadPoint.y + GPSOffsetPixels.y,
5966 nominal_circle_size_pixels / 2);
5967 }
5968 }
5969
5970 // Draw radar rings if activated
5971 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
5972 double factor = 1.00;
5973 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
5974 factor = 1 / 1.852;
5975 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
5976 if (std::isnan(gSog))
5977 factor = 0.0;
5978 else
5979 factor = gSog / 60;
5980 }
5981 factor *= g_fNavAidRadarRingsStep;
5982
5983 double tlat, tlon;
5984 wxPoint r;
5985 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
5986 GetCanvasPointPix(tlat, tlon, &r);
5987
5988 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
5989 pow((double)(lGPSPoint.m_y - r.y), 2));
5990 int pix_radius = (int)lpp;
5991
5992 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
5993
5994 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
5995
5996 dc.SetPen(ppPen1);
5997 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
5998
5999 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6000 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6001 }
6002}
6003
6004void ChartCanvas::ComputeShipScaleFactor(
6005 float icon_hdt, int ownShipWidth, int ownShipLength,
6006 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6007 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6008 float screenResolution = m_pix_per_mm;
6009
6010 // Calculate the true ship length in exact pixels
6011 double ship_bow_lat, ship_bow_lon;
6012 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6013 &ship_bow_lat, &ship_bow_lon);
6014 wxPoint lShipBowPoint;
6015 wxPoint2DDouble b_point =
6016 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6017 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6018
6019 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6020 powf((float)(b_point.m_y - a_point.m_y), 2));
6021
6022 // And in mm
6023 float shipLength_mm = shipLength_px / screenResolution;
6024
6025 // Set minimum ownship drawing size
6026 float ownship_min_mm = g_n_ownship_min_mm;
6027 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6028
6029 // Calculate Nautical Miles distance from midships to gps antenna
6030 float hdt_ant = icon_hdt + 180.;
6031 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6032 float dx = g_n_gps_antenna_offset_x / 1852.;
6033 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6034 {
6035 hdt_ant = icon_hdt;
6036 dy = -dy;
6037 }
6038
6039 // If the drawn ship size is going to be clamped, adjust the gps antenna
6040 // offsets
6041 if (shipLength_mm < ownship_min_mm) {
6042 dy /= shipLength_mm / ownship_min_mm;
6043 dx /= shipLength_mm / ownship_min_mm;
6044 }
6045
6046 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6047
6048 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6049 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6050 &ship_mid_lon1);
6051
6052 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6053 &lShipMidPoint);
6054
6055 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6056 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6057
6058 float scale_factor = shipLength_px / ownShipLength;
6059
6060 // Calculate a scale factor that would produce a reasonably sized icon
6061 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6062
6063 // And choose the correct one
6064 scale_factor = wxMax(scale_factor, scale_factor_min);
6065
6066 scale_factor_y = scale_factor;
6067 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6068 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6069}
6070
6071void ChartCanvas::ShipDraw(ocpnDC &dc) {
6072 if (!GetVP().IsValid()) return;
6073
6074 wxPoint GPSOffsetPixels(0, 0);
6075 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6076
6077 // COG/SOG may be undefined in NMEA data stream
6078 float pCog = std::isnan(gCog) ? 0 : gCog;
6079 float pSog = std::isnan(gSog) ? 0 : gSog;
6080
6081 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6082
6083 lShipMidPoint = lGPSPoint;
6084
6085 // Draw the icon rotated to the COG
6086 // or to the Hdt if available
6087 float icon_hdt = pCog;
6088 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6089
6090 // COG may be undefined in NMEA data stream
6091 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6092
6093 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6094 // predictor
6095 double osd_head_lat, osd_head_lon;
6096 wxPoint osd_head_point;
6097
6098 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6099 &osd_head_lon);
6100
6101 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6102
6103 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6104 (float)(osd_head_point.x - lShipMidPoint.m_x));
6105 icon_rad += (float)PI;
6106
6107 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6108
6109 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6110 // nominal size and is just barely outside the viewport ....
6111 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6112
6113 // TODO: fix to include actual size of boat that will be rendered
6114 int img_height = 0;
6115 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6116 if (GetVP().chart_scale >
6117 300000) // According to S52, this should be 50,000
6118 {
6119 ShipDrawLargeScale(dc, lShipMidPoint);
6120 img_height = 20;
6121 } else {
6122 wxImage pos_image;
6123
6124 // Substitute user ownship image if found
6125 if (m_pos_image_user)
6126 pos_image = m_pos_image_user->Copy();
6127 else if (SHIP_NORMAL == m_ownship_state)
6128 pos_image = m_pos_image_red->Copy();
6129 if (SHIP_LOWACCURACY == m_ownship_state)
6130 pos_image = m_pos_image_yellow->Copy();
6131 else if (SHIP_NORMAL != m_ownship_state)
6132 pos_image = m_pos_image_grey->Copy();
6133
6134 // Substitute user ownship image if found
6135 if (m_pos_image_user) {
6136 pos_image = m_pos_image_user->Copy();
6137
6138 if (SHIP_LOWACCURACY == m_ownship_state)
6139 pos_image = m_pos_image_user_yellow->Copy();
6140 else if (SHIP_NORMAL != m_ownship_state)
6141 pos_image = m_pos_image_user_grey->Copy();
6142 }
6143
6144 img_height = pos_image.GetHeight();
6145
6146 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6147 g_OwnShipIconType > 0) // use large ship
6148 {
6149 int ownShipWidth = 22; // Default values from s_ownship_icon
6150 int ownShipLength = 84;
6151 if (g_OwnShipIconType == 1) {
6152 ownShipWidth = pos_image.GetWidth();
6153 ownShipLength = pos_image.GetHeight();
6154 }
6155
6156 float scale_factor_x, scale_factor_y;
6157 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6158 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6159 scale_factor_x, scale_factor_y);
6160
6161 if (g_OwnShipIconType == 1) { // Scaled bitmap
6162 pos_image.Rescale(ownShipWidth * scale_factor_x,
6163 ownShipLength * scale_factor_y,
6164 wxIMAGE_QUALITY_HIGH);
6165 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6166 wxImage rot_image =
6167 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6168
6169 // Simple sharpening algorithm.....
6170 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6171 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6172 if (rot_image.GetAlpha(ip, jp) > 64)
6173 rot_image.SetAlpha(ip, jp, 255);
6174
6175 wxBitmap os_bm(rot_image);
6176
6177 int w = os_bm.GetWidth();
6178 int h = os_bm.GetHeight();
6179 img_height = h;
6180
6181 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6182 lShipMidPoint.m_y - h / 2, true);
6183
6184 // Maintain dirty box,, missing in __WXMSW__ library
6185 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6186 lShipMidPoint.m_y - h / 2);
6187 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6188 lShipMidPoint.m_y - h / 2 + h);
6189 }
6190
6191 else if (g_OwnShipIconType == 2) { // Scaled Vector
6192 wxPoint ownship_icon[10];
6193
6194 for (int i = 0; i < 10; i++) {
6195 int j = i * 2;
6196 float pxa = (float)(s_ownship_icon[j]);
6197 float pya = (float)(s_ownship_icon[j + 1]);
6198 pya *= scale_factor_y;
6199 pxa *= scale_factor_x;
6200
6201 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6202 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6203
6204 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6205 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6206 }
6207
6208 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6209 dc.SetPen(ppPen1);
6210 dc.SetBrush(wxBrush(ShipColor()));
6211
6212 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6213
6214 // draw reference point (midships) cross
6215 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6216 ownship_icon[7].y);
6217 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6218 ownship_icon[9].y);
6219 }
6220
6221 img_height = ownShipLength * scale_factor_y;
6222
6223 // Reference point, where the GPS antenna is
6224 int circle_rad = 3;
6225 if (m_pos_image_user) circle_rad = 1;
6226
6227 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6228 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6229 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6230 } else { // Fixed bitmap icon.
6231 /* non opengl, or suboptimal opengl via ocpndc: */
6232 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6233 wxImage rot_image =
6234 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6235
6236 // Simple sharpening algorithm.....
6237 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6238 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6239 if (rot_image.GetAlpha(ip, jp) > 64)
6240 rot_image.SetAlpha(ip, jp, 255);
6241
6242 wxBitmap os_bm(rot_image);
6243
6244 if (g_ShipScaleFactorExp > 1) {
6245 wxImage scaled_image = os_bm.ConvertToImage();
6246 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6247 1.0; // soften the scale factor a bit
6248 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6249 scaled_image.GetHeight() * factor,
6250 wxIMAGE_QUALITY_HIGH));
6251 }
6252 int w = os_bm.GetWidth();
6253 int h = os_bm.GetHeight();
6254 img_height = h;
6255
6256 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6257 lShipMidPoint.m_y - h / 2, true);
6258
6259 // Reference point, where the GPS antenna is
6260 int circle_rad = 3;
6261 if (m_pos_image_user) circle_rad = 1;
6262
6263 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6264 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6265 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6266
6267 // Maintain dirty box,, missing in __WXMSW__ library
6268 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6269 lShipMidPoint.m_y - h / 2);
6270 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6271 lShipMidPoint.m_y - h / 2 + h);
6272 }
6273 } // ownship draw
6274 }
6275
6276 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6277}
6278
6279/* @ChartCanvas::CalcGridSpacing
6280 **
6281 ** Calculate the major and minor spacing between the lat/lon grid
6282 **
6283 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6284 *window
6285 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6286 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6287 ** @return [void]
6288 */
6289void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6290 float &MinorSpacing) {
6291 // table for calculating the distance between the grids
6292 // [0] view_scale ppm
6293 // [1] spacing between major grid lines in degrees
6294 // [2] spacing between minor grid lines in degrees
6295 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6296 {.000001f, 45.0f, 15.0f},
6297 {.0002f, 30.0f, 10.0f},
6298 {.0003f, 10.0f, 2.0f},
6299 {.0008f, 5.0f, 1.0f},
6300 {.001f, 2.0f, 30.0f / 60.0f},
6301 {.003f, 1.0f, 20.0f / 60.0f},
6302 {.006f, 0.5f, 10.0f / 60.0f},
6303 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6304 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6305 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6306 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6307 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6308 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6309 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6310 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6311
6312 unsigned int tabi;
6313 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6314 if (view_scale_ppm < lltab[tabi][0]) break;
6315 MajorSpacing = lltab[tabi][1]; // major latitude distance
6316 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6317 return;
6318}
6319/* @ChartCanvas::CalcGridText *************************************
6320 **
6321 ** Calculates text to display at the major grid lines
6322 **
6323 ** @param [r] latlon [float] latitude or longitude of grid line
6324 ** @param [r] spacing [float] distance between two major grid lines
6325 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6326 **
6327 ** @return
6328 */
6329
6330wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6331 int deg = (int)fabs(latlon); // degrees
6332 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6333 char postfix;
6334
6335 // calculate postfix letter (NSEW)
6336 if (latlon > 0.0) {
6337 if (bPostfix) {
6338 postfix = 'N';
6339 } else {
6340 postfix = 'E';
6341 }
6342 } else if (latlon < 0.0) {
6343 if (bPostfix) {
6344 postfix = 'S';
6345 } else {
6346 postfix = 'W';
6347 }
6348 } else {
6349 postfix = ' '; // no postfix for equator and greenwich
6350 }
6351 // calculate text, display minutes only if spacing is smaller than one degree
6352
6353 wxString ret;
6354 if (spacing >= 1.0) {
6355 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6356 } else if (spacing >= (1.0 / 60.0)) {
6357 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6358 } else {
6359 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6360 }
6361
6362 return ret;
6363}
6364
6365/* @ChartCanvas::GridDraw *****************************************
6366 **
6367 ** Draws major and minor Lat/Lon Grid on the chart
6368 ** - distance between Grid-lm ines are calculated automatic
6369 ** - major grid lines will be across the whole chart window
6370 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6371 **
6372 ** @param [w] dc [wxDC&] the wx drawing context
6373 **
6374 ** @return [void]
6375 ************************************************************************/
6376void ChartCanvas::GridDraw(ocpnDC &dc) {
6377 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6378
6379 double nlat, elon, slat, wlon;
6380 float lat, lon;
6381 float dlon;
6382 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6383 wxCoord w, h;
6384 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6385 dc.SetPen(GridPen);
6386 if (!m_pgridFont) SetupGridFont();
6387 dc.SetFont(*m_pgridFont);
6388 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6389
6390 w = m_canvas_width;
6391 h = m_canvas_height;
6392
6393 GetCanvasPixPoint(0, 0, nlat,
6394 wlon); // get lat/lon of upper left point of the window
6395 GetCanvasPixPoint(w, h, slat,
6396 elon); // get lat/lon of lower right point of the window
6397 dlon =
6398 elon -
6399 wlon; // calculate how many degrees of longitude are shown in the window
6400 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6401 {
6402 dlon = dlon + 360.0;
6403 }
6404 // calculate distance between latitude grid lines
6405 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6406
6407 // calculate position of first major latitude grid line
6408 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6409
6410 // Draw Major latitude grid lines and text
6411 while (lat < nlat) {
6412 wxPoint r;
6413 wxString st =
6414 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6415 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6416 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6417 dc.DrawText(st, 0, r.y); // draw text
6418 lat = lat + gridlatMajor;
6419
6420 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6421 }
6422
6423 // calculate position of first minor latitude grid line
6424 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6425
6426 // Draw minor latitude grid lines
6427 while (lat < nlat) {
6428 wxPoint r;
6429 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6430 dc.DrawLine(0, r.y, 10, r.y, false);
6431 dc.DrawLine(w - 10, r.y, w, r.y, false);
6432 lat = lat + gridlatMinor;
6433 }
6434
6435 // calculate distance between grid lines
6436 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6437
6438 // calculate position of first major latitude grid line
6439 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6440
6441 // draw major longitude grid lines
6442 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6443 wxPoint r;
6444 wxString st = CalcGridText(lon, gridlonMajor, false);
6445 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6446 dc.DrawLine(r.x, 0, r.x, h, false);
6447 dc.DrawText(st, r.x, 0);
6448 lon = lon + gridlonMajor;
6449 if (lon > 180.0) {
6450 lon = lon - 360.0;
6451 }
6452
6453 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6454 }
6455
6456 // calculate position of first minor longitude grid line
6457 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6458 // draw minor longitude grid lines
6459 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6460 wxPoint r;
6461 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6462 dc.DrawLine(r.x, 0, r.x, 10, false);
6463 dc.DrawLine(r.x, h - 10, r.x, h, false);
6464 lon = lon + gridlonMinor;
6465 if (lon > 180.0) {
6466 lon = lon - 360.0;
6467 }
6468 }
6469}
6470
6471void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6472 if (0 ) {
6473 double blat, blon, tlat, tlon;
6474 wxPoint r;
6475
6476 int x_origin = m_bDisplayGrid ? 60 : 20;
6477 int y_origin = m_canvas_height - 50;
6478
6479 float dist;
6480 int count;
6481 wxPen pen1, pen2;
6482
6483 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6484 {
6485 dist = 10.0;
6486 count = 5;
6487 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6488 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6489 } else // Draw 1 mile scale as SCALEB10
6490 {
6491 dist = 1.0;
6492 count = 10;
6493 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6494 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6495 }
6496
6497 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6498 double rotation = -VPoint.rotation;
6499
6500 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6501 GetCanvasPointPix(tlat, tlon, &r);
6502 int l1 = (y_origin - r.y) / count;
6503
6504 for (int i = 0; i < count; i++) {
6505 int y = l1 * i;
6506 if (i & 1)
6507 dc.SetPen(pen1);
6508 else
6509 dc.SetPen(pen2);
6510
6511 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6512 }
6513 } else {
6514 double blat, blon, tlat, tlon;
6515
6516 int x_origin = 5.0 * GetPixPerMM();
6517 int chartbar_height = GetChartbarHeight();
6518 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6519 // if (style->chartStatusWindowTransparent)
6520 // chartbar_height = 0;
6521 int y_origin = m_canvas_height - chartbar_height - 5;
6522#ifdef __WXOSX__
6523 if (!g_bopengl)
6524 y_origin =
6525 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6526#endif
6527
6528 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6529 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6530
6531 double d;
6532 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6533 d /= 2;
6534
6535 int unit = g_iDistanceFormat;
6536 if (d < .5 &&
6537 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6538 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6539
6540 // nice number
6541 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6542 float places = floor(logdist), rem = logdist - places;
6543 dist = pow(10, places);
6544
6545 if (rem < .2)
6546 dist /= 5;
6547 else if (rem < .5)
6548 dist /= 2;
6549
6550 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6551 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6552 double rotation = -VPoint.rotation;
6553
6554 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6555 &tlat, &tlon);
6556 wxPoint r;
6557 GetCanvasPointPix(tlat, tlon, &r);
6558 int l1 = r.x - x_origin;
6559
6560 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6561 12); // Store this for later reference
6562
6563 dc.SetPen(pen1);
6564
6565 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6566 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6567 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6568
6569 if (!m_pgridFont) SetupGridFont();
6570 dc.SetFont(*m_pgridFont);
6571 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6572 int w, h;
6573 dc.GetTextExtent(s, &w, &h);
6574 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6575 if (g_bopengl) {
6576 w /= dpi_factor;
6577 h /= dpi_factor;
6578 }
6579 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6580 }
6581}
6582
6583void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6584 // Constants?
6585 double da_min = 2.;
6586 double da_max = 6.;
6587 double ra_min = 0.;
6588 double ra_max = 40.;
6589
6590 wxPen pen_save = dc.GetPen();
6591
6592 wxDateTime now = wxDateTime::Now();
6593
6594 dc.SetPen(pen);
6595
6596 int x0, y0, x1, y1;
6597
6598 x0 = x1 = x + radius; // Start point
6599 y0 = y1 = y;
6600 double angle = 0.;
6601 int i = 0;
6602
6603 while (angle < 360.) {
6604 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6605 angle += da;
6606
6607 if (angle > 360.) angle = 360.;
6608
6609 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6610
6611 double r;
6612 if (i & 1)
6613 r = radius + ra;
6614 else
6615 r = radius - ra;
6616
6617 x1 = (int)(x + cos(angle * PI / 180.) * r);
6618 y1 = (int)(y + sin(angle * PI / 180.) * r);
6619
6620 dc.DrawLine(x0, y0, x1, y1);
6621
6622 x0 = x1;
6623 y0 = y1;
6624
6625 i++;
6626 }
6627
6628 dc.DrawLine(x + radius, y, x1, y1); // closure
6629
6630 dc.SetPen(pen_save);
6631}
6632
6633static bool bAnchorSoundPlaying = false;
6634
6635static void onAnchorSoundFinished(void *ptr) {
6636 g_anchorwatch_sound->UnLoad();
6637 bAnchorSoundPlaying = false;
6638}
6639
6640void ChartCanvas::AlertDraw(ocpnDC &dc) {
6641 // Visual and audio alert for anchorwatch goes here
6642 bool play_sound = false;
6643 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6644 if (AnchorAlertOn1) {
6645 wxPoint TargetPoint;
6646 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
6647 &TargetPoint);
6648 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6649 TargetPoint.y, 100);
6650 play_sound = true;
6651 }
6652 } else
6653 AnchorAlertOn1 = false;
6654
6655 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6656 if (AnchorAlertOn2) {
6657 wxPoint TargetPoint;
6658 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
6659 &TargetPoint);
6660 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6661 TargetPoint.y, 100);
6662 play_sound = true;
6663 }
6664 } else
6665 AnchorAlertOn2 = false;
6666
6667 if (play_sound) {
6668 if (!bAnchorSoundPlaying) {
6669 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6670 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6671 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6672 if (g_anchorwatch_sound->IsOk()) {
6673 bAnchorSoundPlaying = true;
6674 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6675 g_anchorwatch_sound->Play();
6676 }
6677 }
6678 }
6679}
6680
6681void ChartCanvas::UpdateShips() {
6682 // Get the rectangle in the current dc which bounds the "ownship" symbol
6683
6684 wxClientDC dc(this);
6685 if (!dc.IsOk()) return;
6686
6687 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6688 if (!test_bitmap.IsOk()) return;
6689
6690 wxMemoryDC temp_dc(test_bitmap);
6691
6692 temp_dc.ResetBoundingBox();
6693 temp_dc.DestroyClippingRegion();
6694 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6695
6696 // Draw the ownship on the temp_dc
6697 ocpnDC ocpndc = ocpnDC(temp_dc);
6698 ShipDraw(ocpndc);
6699
6700 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6701 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6702 if (p) {
6703 wxPoint px;
6704 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6705 ocpndc.CalcBoundingBox(px.x, px.y);
6706 }
6707 }
6708
6709 ship_draw_rect =
6710 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6711 temp_dc.MaxY() - temp_dc.MinY());
6712
6713 wxRect own_ship_update_rect = ship_draw_rect;
6714
6715 if (!own_ship_update_rect.IsEmpty()) {
6716 // The required invalidate rectangle is the union of the last drawn
6717 // rectangle and this drawn rectangle
6718 own_ship_update_rect.Union(ship_draw_last_rect);
6719 own_ship_update_rect.Inflate(2);
6720 }
6721
6722 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6723
6724 ship_draw_last_rect = ship_draw_rect;
6725
6726 temp_dc.SelectObject(wxNullBitmap);
6727}
6728
6729void ChartCanvas::UpdateAlerts() {
6730 // Get the rectangle in the current dc which bounds the detected Alert
6731 // targets
6732
6733 // Use this dc
6734 wxClientDC dc(this);
6735
6736 // Get dc boundary
6737 int sx, sy;
6738 dc.GetSize(&sx, &sy);
6739
6740 // Need a bitmap
6741 wxBitmap test_bitmap(sx, sy, -1);
6742
6743 // Create a memory DC
6744 wxMemoryDC temp_dc;
6745 temp_dc.SelectObject(test_bitmap);
6746
6747 temp_dc.ResetBoundingBox();
6748 temp_dc.DestroyClippingRegion();
6749 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6750
6751 // Draw the Alert Targets on the temp_dc
6752 ocpnDC ocpndc = ocpnDC(temp_dc);
6753 AlertDraw(ocpndc);
6754
6755 // Retrieve the drawing extents
6756 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6757 temp_dc.MaxX() - temp_dc.MinX(),
6758 temp_dc.MaxY() - temp_dc.MinY());
6759
6760 if (!alert_rect.IsEmpty())
6761 alert_rect.Inflate(2); // clear all drawing artifacts
6762
6763 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6764 // The required invalidate rectangle is the union of the last drawn
6765 // rectangle and this drawn rectangle
6766 wxRect alert_update_rect = alert_draw_rect;
6767 alert_update_rect.Union(alert_rect);
6768
6769 // Invalidate the rectangular region
6770 RefreshRect(alert_update_rect, false);
6771 }
6772
6773 // Save this rectangle for next time
6774 alert_draw_rect = alert_rect;
6775
6776 temp_dc.SelectObject(wxNullBitmap); // clean up
6777}
6778
6779void ChartCanvas::UpdateAIS() {
6780 if (!g_pAIS) return;
6781
6782 // Get the rectangle in the current dc which bounds the detected AIS targets
6783
6784 // Use this dc
6785 wxClientDC dc(this);
6786
6787 // Get dc boundary
6788 int sx, sy;
6789 dc.GetSize(&sx, &sy);
6790
6791 wxRect ais_rect;
6792
6793 // How many targets are there?
6794
6795 // If more than "some number", it will be cheaper to refresh the entire
6796 // screen than to build update rectangles for each target.
6797 if (g_pAIS->GetTargetList().size() > 10) {
6798 ais_rect = wxRect(0, 0, sx, sy); // full screen
6799 } else {
6800 // Need a bitmap
6801 wxBitmap test_bitmap(sx, sy, -1);
6802
6803 // Create a memory DC
6804 wxMemoryDC temp_dc;
6805 temp_dc.SelectObject(test_bitmap);
6806
6807 temp_dc.ResetBoundingBox();
6808 temp_dc.DestroyClippingRegion();
6809 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6810
6811 // Draw the AIS Targets on the temp_dc
6812 ocpnDC ocpndc = ocpnDC(temp_dc);
6813 AISDraw(ocpndc, GetVP(), this);
6814 AISDrawAreaNotices(ocpndc, GetVP(), this);
6815
6816 // Retrieve the drawing extents
6817 ais_rect =
6818 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6819 temp_dc.MaxY() - temp_dc.MinY());
6820
6821 if (!ais_rect.IsEmpty())
6822 ais_rect.Inflate(2); // clear all drawing artifacts
6823
6824 temp_dc.SelectObject(wxNullBitmap); // clean up
6825 }
6826
6827 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6828 // The required invalidate rectangle is the union of the last drawn
6829 // rectangle and this drawn rectangle
6830 wxRect ais_update_rect = ais_draw_rect;
6831 ais_update_rect.Union(ais_rect);
6832
6833 // Invalidate the rectangular region
6834 RefreshRect(ais_update_rect, false);
6835 }
6836
6837 // Save this rectangle for next time
6838 ais_draw_rect = ais_rect;
6839}
6840
6841void ChartCanvas::ToggleCPAWarn() {
6842 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6843 wxString mess;
6844 if (g_bCPAWarn) {
6845 g_bTCPA_Max = true;
6846 mess = _("ON");
6847 } else {
6848 g_bTCPA_Max = false;
6849 mess = _("OFF");
6850 }
6851 // Print to status bar if available.
6852 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
6853 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
6854 } else {
6855 if (!g_AisFirstTimeUse) {
6856 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
6857 _("CPA") + " " + mess, 4, 4);
6858 }
6859 }
6860}
6861
6862void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
6863
6864void ChartCanvas::OnSize(wxSizeEvent &event) {
6865 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
6866 // GetClientSize returns the size of the canvas area in logical pixels.
6867 GetClientSize(&m_canvas_width, &m_canvas_height);
6868
6869#ifdef __WXOSX__
6870 // Support scaled HDPI displays.
6871 m_displayScale = GetContentScaleFactor();
6872#endif
6873
6874 // Convert to physical pixels.
6875 m_canvas_width *= m_displayScale;
6876 m_canvas_height *= m_displayScale;
6877
6878 // Resize the current viewport
6879 VPoint.pix_width = m_canvas_width;
6880 VPoint.pix_height = m_canvas_height;
6881 VPoint.SetPixelScale(m_displayScale);
6882
6883 // Get some canvas metrics
6884
6885 // Rescale to current value, in order to rebuild VPoint data
6886 // structures for new canvas size
6888
6889 m_absolute_min_scale_ppm =
6890 m_canvas_width /
6891 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
6892
6893 // Inform the parent Frame that I am being resized...
6894 gFrame->ProcessCanvasResize();
6895
6896 // if MUIBar is active, size the bar
6897 // if(g_useMUI && !m_muiBar){ // rebuild if
6898 // necessary
6899 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
6900 // m_muiBarHOSize = m_muiBar->GetSize();
6901 // }
6902
6903 if (m_muiBar) {
6904 SetMUIBarPosition();
6905 UpdateFollowButtonState();
6906 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
6907 }
6908
6909 // Set up the scroll margins
6910 xr_margin = m_canvas_width * 95 / 100;
6911 xl_margin = m_canvas_width * 5 / 100;
6912 yt_margin = m_canvas_height * 5 / 100;
6913 yb_margin = m_canvas_height * 95 / 100;
6914
6915 if (m_pQuilt)
6916 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
6917
6918 // Resize the scratch BM
6919 delete pscratch_bm;
6920 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
6921 m_brepaint_piano = true;
6922
6923 // Resize the Route Calculation BM
6924 m_dc_route.SelectObject(wxNullBitmap);
6925 delete proute_bm;
6926 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
6927 m_dc_route.SelectObject(*proute_bm);
6928
6929 // Resize the saved Bitmap
6930 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
6931
6932 // Resize the working Bitmap
6933 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
6934
6935 // Rescale again, to capture all the changes for new canvas size
6937
6938#ifdef ocpnUSE_GL
6939 if (/*g_bopengl &&*/ m_glcc) {
6940 // FIXME (dave) This can go away?
6941 m_glcc->OnSize(event);
6942 }
6943#endif
6944
6945 FormatPianoKeys();
6946 // Invalidate the whole window
6947 ReloadVP();
6948}
6949
6950void ChartCanvas::ProcessNewGUIScale() {
6951 // m_muiBar->Hide();
6952 delete m_muiBar;
6953 m_muiBar = 0;
6954
6955 CreateMUIBar();
6956}
6957
6958void ChartCanvas::CreateMUIBar() {
6959 if (g_useMUI && !m_muiBar) { // rebuild if necessary
6960
6961 // We need to update the m_bENCGroup flag, at least for the initial creation
6962 // of a MUIBar
6963 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
6964
6965 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
6966 m_muiBar->SetColorScheme(m_cs);
6967 m_muiBarHOSize = m_muiBar->m_size;
6968 }
6969
6970 if (m_muiBar) {
6971 SetMUIBarPosition();
6972 UpdateFollowButtonState();
6973 m_muiBar->UpdateDynamicValues();
6974 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
6975 }
6976}
6977
6978void ChartCanvas::SetMUIBarPosition() {
6979 // if MUIBar is active, size the bar
6980 if (m_muiBar) {
6981 // We estimate the piano width based on the canvas width
6982 int pianoWidth = GetClientSize().x * 0.6f;
6983 // If the piano already exists, we can use its exact width
6984 // if(m_Piano)
6985 // pianoWidth = m_Piano->GetWidth();
6986
6987 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
6988 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
6989 delete m_muiBar;
6990 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
6991 m_muiBar->SetColorScheme(m_cs);
6992 }
6993 }
6994
6995 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
6996 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
6997 delete m_muiBar;
6998 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
6999 m_muiBar->SetColorScheme(m_cs);
7000 }
7001 }
7002
7003 m_muiBar->SetBestPosition();
7004 }
7005}
7006
7007void ChartCanvas::DestroyMuiBar() {
7008 if (m_muiBar) {
7009 delete m_muiBar;
7010 m_muiBar = NULL;
7011 }
7012}
7013
7014void ChartCanvas::ShowCompositeInfoWindow(
7015 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7016 if (n_charts > 0) {
7017 if (NULL == m_pCIWin) {
7018 m_pCIWin = new ChInfoWin(this);
7019 m_pCIWin->Hide();
7020 }
7021
7022 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7023 wxString s;
7024
7025 s = _("Composite of ");
7026
7027 wxString s1;
7028 s1.Printf("%d ", n_charts);
7029 if (n_charts > 1)
7030 s1 += _("charts");
7031 else
7032 s1 += _("chart");
7033 s += s1;
7034 s += '\n';
7035
7036 s1.Printf(_("Chart scale"));
7037 s1 += ": ";
7038 wxString s2;
7039 s2.Printf("1:%d\n", scale);
7040 s += s1;
7041 s += s2;
7042
7043 s1 = _("Zoom in for more information");
7044 s += s1;
7045 s += '\n';
7046
7047 int char_width = s1.Length();
7048 int char_height = 3;
7049
7050 if (g_bChartBarEx) {
7051 s += '\n';
7052 int j = 0;
7053 for (int i : index_vector) {
7054 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7055 wxString path = cte.GetFullSystemPath();
7056 s += path;
7057 s += '\n';
7058 char_height++;
7059 char_width = wxMax(char_width, path.Length());
7060 if (j++ >= 9) break;
7061 }
7062 if (j >= 9) {
7063 s += " .\n .\n .\n";
7064 char_height += 3;
7065 }
7066 s += '\n';
7067 char_height += 1;
7068
7069 char_width += 4; // Fluff
7070 }
7071
7072 m_pCIWin->SetString(s);
7073
7074 m_pCIWin->FitToChars(char_width, char_height);
7075
7076 wxPoint p;
7077 p.x = x / GetContentScaleFactor();
7078 if ((p.x + m_pCIWin->GetWinSize().x) >
7079 (m_canvas_width / GetContentScaleFactor()))
7080 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7081 m_pCIWin->GetWinSize().x) /
7082 2; // centered
7083
7084 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7085 4 - m_pCIWin->GetWinSize().y;
7086
7087 m_pCIWin->dbIndex = 0;
7088 m_pCIWin->chart_scale = 0;
7089 m_pCIWin->SetPosition(p);
7090 m_pCIWin->SetBitmap();
7091 m_pCIWin->Refresh();
7092 m_pCIWin->Show();
7093 }
7094 } else {
7095 HideChartInfoWindow();
7096 }
7097}
7098
7099void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7100 if (dbIndex >= 0) {
7101 if (NULL == m_pCIWin) {
7102 m_pCIWin = new ChInfoWin(this);
7103 m_pCIWin->Hide();
7104 }
7105
7106 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7107 wxString s;
7108 ChartBase *pc = NULL;
7109
7110 // TOCTOU race but worst case will reload chart.
7111 // need to lock it or the background spooler may evict charts in
7112 // OpenChartFromDBAndLock
7113 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7114 pc = ChartData->OpenChartFromDBAndLock(
7115 dbIndex, FULL_INIT); // this must come from cache
7116
7117 int char_width, char_height;
7118 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7119 if (pc) ChartData->UnLockCacheChart(dbIndex);
7120
7121 m_pCIWin->SetString(s);
7122 m_pCIWin->FitToChars(char_width, char_height);
7123
7124 wxPoint p;
7125 p.x = x / GetContentScaleFactor();
7126 if ((p.x + m_pCIWin->GetWinSize().x) >
7127 (m_canvas_width / GetContentScaleFactor()))
7128 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7129 m_pCIWin->GetWinSize().x) /
7130 2; // centered
7131
7132 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7133 4 - m_pCIWin->GetWinSize().y;
7134
7135 m_pCIWin->dbIndex = dbIndex;
7136 m_pCIWin->SetPosition(p);
7137 m_pCIWin->SetBitmap();
7138 m_pCIWin->Refresh();
7139 m_pCIWin->Show();
7140 }
7141 } else {
7142 HideChartInfoWindow();
7143 }
7144}
7145
7146void ChartCanvas::HideChartInfoWindow(void) {
7147 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7148 m_pCIWin->Hide();
7149 m_pCIWin->Destroy();
7150 m_pCIWin = NULL;
7151
7152#ifdef __ANDROID__
7153 androidForceFullRepaint();
7154#endif
7155 }
7156}
7157
7158void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7159 wxMouseEvent ev(wxEVT_MOTION);
7160 ev.m_x = mouse_x;
7161 ev.m_y = mouse_y;
7162 ev.m_leftDown = mouse_leftisdown;
7163
7164 wxEvtHandler *evthp = GetEventHandler();
7165
7166 ::wxPostEvent(evthp, ev);
7167}
7168
7169void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7170 if ((m_panx_target_final - m_panx_target_now) ||
7171 (m_pany_target_final - m_pany_target_now)) {
7172 DoTimedMovementTarget();
7173 } else
7174 DoTimedMovement();
7175}
7176
7177void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7178
7179bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7180 int delta) {
7181 if (m_disable_edge_pan) return false;
7182
7183 bool bft = false;
7184 int pan_margin = m_canvas_width * margin / 100;
7185 int pan_timer_set = 200;
7186 double pan_delta = GetVP().pix_width * delta / 100;
7187 int pan_x = 0;
7188 int pan_y = 0;
7189
7190 if (x > m_canvas_width - pan_margin) {
7191 bft = true;
7192 pan_x = pan_delta;
7193 }
7194
7195 else if (x < pan_margin) {
7196 bft = true;
7197 pan_x = -pan_delta;
7198 }
7199
7200 if (y < pan_margin) {
7201 bft = true;
7202 pan_y = -pan_delta;
7203 }
7204
7205 else if (y > m_canvas_height - pan_margin) {
7206 bft = true;
7207 pan_y = pan_delta;
7208 }
7209
7210 // Of course, if dragging, and the mouse left button is not down, we must
7211 // stop the event injection
7212 if (bdragging) {
7213 if (!g_btouch) {
7214 wxMouseState state = ::wxGetMouseState();
7215#if wxCHECK_VERSION(3, 0, 0)
7216 if (!state.LeftIsDown())
7217#else
7218 if (!state.LeftDown())
7219#endif
7220 bft = false;
7221 }
7222 }
7223 if ((bft) && !pPanTimer->IsRunning()) {
7224 PanCanvas(pan_x, pan_y);
7225 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7226 return true;
7227 }
7228
7229 // This mouse event must not be due to pan timer event injector
7230 // Mouse is out of the pan zone, so prevent any orphan event injection
7231 if ((!bft) && pPanTimer->IsRunning()) {
7232 pPanTimer->Stop();
7233 }
7234
7235 return (false);
7236}
7237
7238// Look for waypoints at the current position.
7239// Used to determine what a mouse event should act on.
7240
7241void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7242 bool setBeingEdited) {
7243 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7244 m_pRoutePointEditTarget = NULL;
7245 m_pFoundPoint = NULL;
7246
7247 SelectItem *pFind = NULL;
7248 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7249 SelectableItemList SelList = pSelect->FindSelectionList(
7250 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7251 wxSelectableItemListNode *node = SelList.GetFirst();
7252 while (node) {
7253 pFind = node->GetData();
7254
7255 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7256
7257 // Get an array of all routes using this point
7258 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7259 // TODO: delete m_pEditRouteArray after use?
7260
7261 // Use route array to determine actual visibility for the point
7262 bool brp_viz = false;
7263 if (m_pEditRouteArray) {
7264 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7265 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7266 if (pr->IsVisible()) {
7267 brp_viz = true;
7268 break;
7269 }
7270 }
7271 } else
7272 brp_viz = frp->IsVisible(); // isolated point
7273
7274 if (brp_viz) {
7275 // Use route array to rubberband all affected routes
7276 if (m_pEditRouteArray) // Editing Waypoint as part of route
7277 {
7278 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7279 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7280 pr->m_bIsBeingEdited = setBeingEdited;
7281 }
7282 m_bRouteEditing = setBeingEdited;
7283 } else // editing Mark
7284 {
7285 frp->m_bRPIsBeingEdited = setBeingEdited;
7286 m_bMarkEditing = setBeingEdited;
7287 }
7288
7289 m_pRoutePointEditTarget = frp;
7290 m_pFoundPoint = pFind;
7291 break; // out of the while(node)
7292 }
7293
7294 node = node->GetNext();
7295 } // while (node)
7296}
7297std::shared_ptr<PI_PointContext> ChartCanvas::GetCanvasContextAtPoint(int x,
7298 int y) {
7299 // General Right Click
7300 // Look for selectable objects
7301 double slat, slon;
7302 GetCanvasPixPoint(x, y, slat, slon);
7303
7304 SelectItem *pFindAIS;
7305 SelectItem *pFindRP;
7306 SelectItem *pFindRouteSeg;
7307 SelectItem *pFindTrackSeg;
7308 SelectItem *pFindCurrent = NULL;
7309 SelectItem *pFindTide = NULL;
7310
7311 // Get all the selectable things at the selected point
7312 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7313 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7314 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7315 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7316 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7317
7318 if (m_bShowCurrent)
7319 pFindCurrent =
7320 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7321
7322 if (m_bShowTide) // look for tide stations
7323 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7324
7325 int seltype = 0;
7326
7327 // Try for AIS targets first
7328 int FoundAIS_MMSI = 0;
7329 if (pFindAIS) {
7330 FoundAIS_MMSI = pFindAIS->GetUserData();
7331
7332 // Make sure the target data is available
7333 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7334 seltype |= SELTYPE_AISTARGET;
7335 }
7336
7337 // Now the various Route Parts
7338
7339 RoutePoint *FoundRoutePoint = NULL;
7340 Route *SelectedRoute = NULL;
7341
7342 if (pFindRP) {
7343 RoutePoint *pFirstVizPoint = NULL;
7344 RoutePoint *pFoundActiveRoutePoint = NULL;
7345 RoutePoint *pFoundVizRoutePoint = NULL;
7346 Route *pSelectedActiveRoute = NULL;
7347 Route *pSelectedVizRoute = NULL;
7348
7349 // There is at least one routepoint, so get the whole list
7350 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7351 SelectableItemList SelList =
7352 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7353 wxSelectableItemListNode *node = SelList.GetFirst();
7354 while (node) {
7355 SelectItem *pFindSel = node->GetData();
7356
7357 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7358
7359 // Get an array of all routes using this point
7360 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7361
7362 // Use route array (if any) to determine actual visibility for this point
7363 bool brp_viz = false;
7364 if (proute_array) {
7365 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7366 Route *pr = (Route *)proute_array->Item(ir);
7367 if (pr->IsVisible()) {
7368 brp_viz = true;
7369 break;
7370 }
7371 }
7372 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7373 // but still exists as a waypoint
7374 brp_viz = prp->IsVisible(); // so treat as isolated point
7375
7376 } else
7377 brp_viz = prp->IsVisible(); // isolated point
7378
7379 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7380
7381 // Use route array to choose the appropriate route
7382 // Give preference to any active route, otherwise select the first visible
7383 // route in the array for this point
7384 if (proute_array) {
7385 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7386 Route *pr = (Route *)proute_array->Item(ir);
7387 if (pr->m_bRtIsActive) {
7388 pSelectedActiveRoute = pr;
7389 pFoundActiveRoutePoint = prp;
7390 break;
7391 }
7392 }
7393
7394 if (NULL == pSelectedVizRoute) {
7395 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7396 Route *pr = (Route *)proute_array->Item(ir);
7397 if (pr->IsVisible()) {
7398 pSelectedVizRoute = pr;
7399 pFoundVizRoutePoint = prp;
7400 break;
7401 }
7402 }
7403 }
7404
7405 delete proute_array;
7406 }
7407
7408 node = node->GetNext();
7409 }
7410
7411 // Now choose the "best" selections
7412 if (pFoundActiveRoutePoint) {
7413 FoundRoutePoint = pFoundActiveRoutePoint;
7414 SelectedRoute = pSelectedActiveRoute;
7415 } else if (pFoundVizRoutePoint) {
7416 FoundRoutePoint = pFoundVizRoutePoint;
7417 SelectedRoute = pSelectedVizRoute;
7418 } else
7419 // default is first visible point in list
7420 FoundRoutePoint = pFirstVizPoint;
7421
7422 if (SelectedRoute) {
7423 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7424 } else if (FoundRoutePoint) {
7425 seltype |= SELTYPE_MARKPOINT;
7426 }
7427
7428 // Highlight the selected point, to verify the proper right click selection
7429#if 0
7430 if (m_pFoundRoutePoint) {
7431 m_pFoundRoutePoint->m_bPtIsSelected = true;
7432 wxRect wp_rect;
7433 RoutePointGui(*m_pFoundRoutePoint)
7434 .CalculateDCRect(m_dc_route, this, &wp_rect);
7435 RefreshRect(wp_rect, true);
7436 }
7437#endif
7438 }
7439
7440 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7441 // routes But call the popup handler with identifier appropriate to the type
7442 if (pFindRouteSeg) // there is at least one select item
7443 {
7444 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7445 SelectableItemList SelList =
7446 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7447
7448 if (NULL == SelectedRoute) // the case where a segment only is selected
7449 {
7450 // Choose the first visible route containing segment in the list
7451 wxSelectableItemListNode *node = SelList.GetFirst();
7452 while (node) {
7453 SelectItem *pFindSel = node->GetData();
7454
7455 Route *pr = (Route *)pFindSel->m_pData3;
7456 if (pr->IsVisible()) {
7457 SelectedRoute = pr;
7458 break;
7459 }
7460 node = node->GetNext();
7461 }
7462 }
7463
7464 if (SelectedRoute) {
7465 if (NULL == FoundRoutePoint)
7466 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7467
7468 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7469 seltype |= SELTYPE_ROUTESEGMENT;
7470 }
7471 }
7472
7473#if 0
7474 if (pFindTrackSeg) {
7475 m_pSelectedTrack = NULL;
7476 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7477 SelectableItemList SelList =
7478 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7479
7480 // Choose the first visible track containing segment in the list
7481 wxSelectableItemListNode *node = SelList.GetFirst();
7482 while (node) {
7483 SelectItem *pFindSel = node->GetData();
7484
7485 Track *pt = (Track *)pFindSel->m_pData3;
7486 if (pt->IsVisible()) {
7487 m_pSelectedTrack = pt;
7488 break;
7489 }
7490 node = node->GetNext();
7491 }
7492
7493 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7494 }
7495#endif
7496
7497#if 0
7498 bool bseltc = false;
7499 // if(0 == seltype)
7500 {
7501 if (pFindCurrent) {
7502 // There may be multiple current entries at the same point.
7503 // For example, there often is a current substation (with directions
7504 // specified) co-located with its master. We want to select the
7505 // substation, so that the direction will be properly indicated on the
7506 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
7507 // substation)
7508 IDX_entry *pIDX_best_candidate;
7509
7510 SelectItem *pFind = NULL;
7511 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7512 SelectableItemList SelList = pSelectTC->FindSelectionList(
7513 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_CURRENTPOINT);
7514
7515 // Default is first entry
7516 wxSelectableItemListNode *node = SelList.GetFirst();
7517 pFind = node->GetData();
7518 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7519
7520 if (SelList.GetCount() > 1) {
7521 node = node->GetNext();
7522 while (node) {
7523 pFind = node->GetData();
7524 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
7525 if (pIDX_candidate->IDX_type == 'c') {
7526 pIDX_best_candidate = pIDX_candidate;
7527 break;
7528 }
7529
7530 node = node->GetNext();
7531 } // while (node)
7532 } else {
7533 wxSelectableItemListNode *node = SelList.GetFirst();
7534 pFind = node->GetData();
7535 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7536 }
7537
7538 m_pIDXCandidate = pIDX_best_candidate;
7539
7540 if (0 == seltype) {
7541 DrawTCWindow(x, y, (void *)pIDX_best_candidate);
7542 Refresh(false);
7543 bseltc = true;
7544 } else
7545 seltype |= SELTYPE_CURRENTPOINT;
7546 }
7547
7548 else if (pFindTide) {
7549 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
7550
7551 if (0 == seltype) {
7552 DrawTCWindow(x, y, (void *)pFindTide->m_pData1);
7553 Refresh(false);
7554 bseltc = true;
7555 } else
7556 seltype |= SELTYPE_TIDEPOINT;
7557 }
7558 }
7559#endif
7560
7561 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7562
7563 // Populate the return struct
7564 auto rstruct = std::make_shared<PI_PointContext>();
7565 rstruct->object_type = OBJECT_CHART;
7566 rstruct->object_ident = "";
7567
7568 if (seltype == SELTYPE_AISTARGET) {
7569 rstruct->object_type = OBJECT_AISTARGET;
7570 wxString val;
7571 val.Printf("%d", FoundAIS_MMSI);
7572 rstruct->object_ident = val.ToStdString();
7573 } else if (seltype & SELTYPE_MARKPOINT) {
7574 if (FoundRoutePoint) {
7575 rstruct->object_type = OBJECT_ROUTEPOINT;
7576 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7577 }
7578 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7579 if (SelectedRoute) {
7580 rstruct->object_type = OBJECT_ROUTESEGMENT;
7581 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7582 }
7583 }
7584
7585 return rstruct;
7586}
7587
7588void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7589 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7590 singleClickEventIsValid = false;
7591 m_DoubleClickTimer->Stop();
7592}
7593
7594bool leftIsDown;
7595
7596bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7597 if (!m_bChartDragging && !m_bDrawingRoute) {
7598 /*
7599 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7600 * mouse event coordinates are in logical pixels.
7601 */
7602 if (m_Compass && m_Compass->IsShown()) {
7603 wxRect logicalRect = m_Compass->GetLogicalRect();
7604 bool isInCompass = logicalRect.Contains(event.GetPosition());
7605 if (isInCompass) {
7606 if (m_Compass->MouseEvent(event)) {
7607 cursor_region = CENTER;
7608 if (!g_btouch) SetCanvasCursor(event);
7609 return true;
7610 }
7611 }
7612 }
7613
7614 if (m_notification_button && m_notification_button->IsShown()) {
7615 wxRect logicalRect = m_notification_button->GetLogicalRect();
7616 bool isinButton = logicalRect.Contains(event.GetPosition());
7617 if (isinButton) {
7618 SetCursor(*pCursorArrow);
7619 if (event.LeftDown()) HandleNotificationMouseClick();
7620 return true;
7621 }
7622 }
7623
7624 if (MouseEventToolbar(event)) return true;
7625
7626 if (MouseEventChartBar(event)) return true;
7627
7628 if (MouseEventMUIBar(event)) return true;
7629
7630 if (MouseEventIENCBar(event)) return true;
7631 }
7632 return false;
7633}
7634
7635void ChartCanvas::HandleNotificationMouseClick() {
7636 if (!m_NotificationsList) {
7637 m_NotificationsList = new NotificationsList(this);
7638
7639 // calculate best size for Notification list
7640 m_NotificationsList->RecalculateSize();
7641 m_NotificationsList->Hide();
7642 }
7643
7644 if (m_NotificationsList->IsShown()) {
7645 m_NotificationsList->Hide();
7646 } else {
7647 m_NotificationsList->RecalculateSize();
7648 m_NotificationsList->ReloadNotificationList();
7649 m_NotificationsList->Show();
7650 }
7651}
7652bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7653 if (!g_bShowChartBar) return false;
7654
7655 if (!m_Piano->MouseEvent(event)) return false;
7656
7657 cursor_region = CENTER;
7658 if (!g_btouch) SetCanvasCursor(event);
7659 return true;
7660}
7661
7662bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7663 if (!IsPrimaryCanvas()) return false;
7664
7665 if (g_MainToolbar) {
7666 if (!g_MainToolbar->MouseEvent(event))
7667 return false;
7668 else
7669 g_MainToolbar->RefreshToolbar();
7670 }
7671
7672 cursor_region = CENTER;
7673 if (!g_btouch) SetCanvasCursor(event);
7674 return true;
7675}
7676
7677bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7678 if (!IsPrimaryCanvas()) return false;
7679
7680 if (g_iENCToolbar) {
7681 if (!g_iENCToolbar->MouseEvent(event))
7682 return false;
7683 else {
7684 g_iENCToolbar->RefreshToolbar();
7685 return true;
7686 }
7687 }
7688 return false;
7689}
7690
7691bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7692 if (m_muiBar) {
7693 if (!m_muiBar->MouseEvent(event)) return false;
7694 }
7695
7696 cursor_region = CENTER;
7697 if (!g_btouch) SetCanvasCursor(event);
7698 if (m_muiBar)
7699 return true;
7700 else
7701 return false;
7702}
7703
7704bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7705 int x, y;
7706
7707 bool bret = false;
7708
7709 event.GetPosition(&x, &y);
7710
7711 x *= m_displayScale;
7712 y *= m_displayScale;
7713
7714 m_MouseDragging = event.Dragging();
7715
7716 // Some systems produce null drag events, where the pointer position has not
7717 // changed from the previous value. Detect this case, and abort further
7718 // processing (FS#1748)
7719#ifdef __WXMSW__
7720 if (event.Dragging()) {
7721 if ((x == mouse_x) && (y == mouse_y)) return true;
7722 }
7723#endif
7724
7725 mouse_x = x;
7726 mouse_y = y;
7727 mouse_leftisdown = event.LeftDown();
7729
7730 // Establish the event region
7731 cursor_region = CENTER;
7732
7733 int chartbar_height = GetChartbarHeight();
7734
7735 if (m_Compass && m_Compass->IsShown() &&
7736 m_Compass->GetRect().Contains(event.GetPosition())) {
7737 cursor_region = CENTER;
7738 } else if (x > xr_margin) {
7739 cursor_region = MID_RIGHT;
7740 } else if (x < xl_margin) {
7741 cursor_region = MID_LEFT;
7742 } else if (y > yb_margin - chartbar_height &&
7743 y < m_canvas_height - chartbar_height) {
7744 cursor_region = MID_TOP;
7745 } else if (y < yt_margin) {
7746 cursor_region = MID_BOT;
7747 } else {
7748 cursor_region = CENTER;
7749 }
7750
7751 if (!g_btouch) SetCanvasCursor(event);
7752
7753 // Protect from leftUp's coming from event handlers in child
7754 // windows who return focus to the canvas.
7755 leftIsDown = event.LeftDown();
7756
7757#ifndef __WXOSX__
7758 if (event.LeftDown()) {
7759 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7760 // The menu bar is temporarily visible due to alt having been pressed.
7761 // Clicking will hide it, and do nothing else.
7762 g_bTempShowMenuBar = false;
7763 parent_frame->ApplyGlobalSettings(false);
7764 return (true);
7765 }
7766 }
7767#endif
7768
7769 // Update modifiers here; some window managers never send the key event
7770 m_modkeys = 0;
7771 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7772 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7773
7774#ifdef __WXMSW__
7775 // TODO Test carefully in other platforms, remove ifdef....
7776 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7777 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7778#endif
7779
7780 event.SetEventObject(this);
7781 if (SendMouseEventToPlugins(event))
7782 return (true); // PlugIn did something, and does not want the canvas to
7783 // do anything else
7784
7785 // Capture LeftUp's and time them, unless it already came from the timer.
7786
7787 // Detect end of chart dragging
7788 if (g_btouch && m_bChartDragging && event.LeftUp()) {
7789 StartChartDragInertia();
7790 }
7791
7792 if (b_handle_dclick && event.LeftUp() && !singleClickEventIsValid) {
7793 // Ignore the second LeftUp after the DClick.
7794 if (m_DoubleClickTimer->IsRunning()) {
7795 m_DoubleClickTimer->Stop();
7796 return (true);
7797 }
7798
7799 // Save the event for later running if there is no DClick.
7800 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7801 singleClickEvent = event;
7802 singleClickEventIsValid = true;
7803 return (true);
7804 }
7805
7806 // This logic is necessary on MSW to handle the case where
7807 // a context (right-click) menu is dismissed without action
7808 // by clicking on the chart surface.
7809 // We need to avoid an unintentional pan by eating some clicks...
7810#ifdef __WXMSW__
7811 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7812 if (g_click_stop > 0) {
7813 g_click_stop--;
7814 return (true);
7815 }
7816 }
7817#endif
7818
7819 // Kick off the Rotation control timer
7820 if (GetUpMode() == COURSE_UP_MODE) {
7821 m_b_rot_hidef = false;
7822 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7823 } else
7824 pRotDefTimer->Stop();
7825
7826 // Retrigger the route leg / AIS target popup timer
7827 bool bRoll = !g_btouch;
7828#ifdef __ANDROID__
7829 bRoll = g_bRollover;
7830#endif
7831 if (bRoll) {
7832 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7833 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7834 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7835 m_RolloverPopupTimer.Start(
7836 10,
7837 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7838 else
7839 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7840 }
7841
7842 // Retrigger the cursor tracking timer
7843 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7844
7845// Show cursor position on Status Bar, if present
7846// except for GTK, under which status bar updates are very slow
7847// due to Update() call.
7848// In this case, as a workaround, update the status window
7849// after an interval timer (pCurTrackTimer) pops, which will happen
7850// whenever the mouse has stopped moving for specified interval.
7851// See the method OnCursorTrackTimerEvent()
7852#if !defined(__WXGTK__) && !defined(__WXQT__)
7853 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7854#endif
7855
7856 // Send the current cursor lat/lon to all PlugIns requesting it
7857 if (g_pi_manager) {
7858 // Occasionally, MSW will produce nonsense events on right click....
7859 // This results in an error in cursor geo position, so we skip this case
7860 if ((x >= 0) && (y >= 0))
7861 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7862 }
7863
7864 if (!g_btouch) {
7865 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7866 wxPoint p = ClientToScreen(wxPoint(x, y));
7867 }
7868 }
7869
7870 if (1 ) {
7871 // Route Creation Rubber Banding
7872 if (m_routeState >= 2) {
7873 r_rband.x = x;
7874 r_rband.y = y;
7875 m_bDrawingRoute = true;
7876
7877 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7878 Refresh(false);
7879 }
7880
7881 // Measure Tool Rubber Banding
7882 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7883 r_rband.x = x;
7884 r_rband.y = y;
7885 m_bDrawingRoute = true;
7886
7887 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7888 Refresh(false);
7889 }
7890 }
7891 return bret;
7892}
7893
7894int ChartCanvas::PrepareContextSelections(double lat, double lon) {
7895 // On general Right Click
7896 // Look for selectable objects
7897 double slat = lat;
7898 double slon = lon;
7899
7900#if defined(__WXMAC__) || defined(__ANDROID__)
7901 wxScreenDC sdc;
7902 ocpnDC dc(sdc);
7903#else
7904 wxClientDC cdc(GetParent());
7905 ocpnDC dc(cdc);
7906#endif
7907
7908 SelectItem *pFindAIS;
7909 SelectItem *pFindRP;
7910 SelectItem *pFindRouteSeg;
7911 SelectItem *pFindTrackSeg;
7912 SelectItem *pFindCurrent = NULL;
7913 SelectItem *pFindTide = NULL;
7914
7915 // Deselect any current objects
7916 if (m_pSelectedRoute) {
7917 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7918 m_pSelectedRoute->DeSelectRoute();
7919#ifdef ocpnUSE_GL
7920 if (g_bopengl && m_glcc) {
7921 InvalidateGL();
7922 Update();
7923 } else
7924#endif
7925 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7926 }
7927
7928 if (m_pFoundRoutePoint) {
7929 m_pFoundRoutePoint->m_bPtIsSelected = false;
7930 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
7931 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
7932 }
7933
7936 if (g_btouch && m_pRoutePointEditTarget) {
7937 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7938 m_pRoutePointEditTarget->m_bPtIsSelected = false;
7939 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7940 }
7941
7942 // Get all the selectable things at the cursor
7943 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7944 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7945 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7946 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7947 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7948
7949 if (m_bShowCurrent)
7950 pFindCurrent =
7951 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7952
7953 if (m_bShowTide) // look for tide stations
7954 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7955
7956 int seltype = 0;
7957
7958 // Try for AIS targets first
7959 if (pFindAIS) {
7960 m_FoundAIS_MMSI = pFindAIS->GetUserData();
7961
7962 // Make sure the target data is available
7963 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
7964 seltype |= SELTYPE_AISTARGET;
7965 }
7966
7967 // Now examine the various Route parts
7968
7969 m_pFoundRoutePoint = NULL;
7970 if (pFindRP) {
7971 RoutePoint *pFirstVizPoint = NULL;
7972 RoutePoint *pFoundActiveRoutePoint = NULL;
7973 RoutePoint *pFoundVizRoutePoint = NULL;
7974 Route *pSelectedActiveRoute = NULL;
7975 Route *pSelectedVizRoute = NULL;
7976
7977 // There is at least one routepoint, so get the whole list
7978 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7979 SelectableItemList SelList =
7980 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7981 wxSelectableItemListNode *node = SelList.GetFirst();
7982 while (node) {
7983 SelectItem *pFindSel = node->GetData();
7984
7985 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7986
7987 // Get an array of all routes using this point
7988 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7989
7990 // Use route array (if any) to determine actual visibility for this point
7991 bool brp_viz = false;
7992 if (proute_array) {
7993 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7994 Route *pr = (Route *)proute_array->Item(ir);
7995 if (pr->IsVisible()) {
7996 brp_viz = true;
7997 break;
7998 }
7999 }
8000 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8001 // but still exists as a waypoint
8002 brp_viz = prp->IsVisible(); // so treat as isolated point
8003
8004 } else
8005 brp_viz = prp->IsVisible(); // isolated point
8006
8007 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8008
8009 // Use route array to choose the appropriate route
8010 // Give preference to any active route, otherwise select the first visible
8011 // route in the array for this point
8012 m_pSelectedRoute = NULL;
8013 if (proute_array) {
8014 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8015 Route *pr = (Route *)proute_array->Item(ir);
8016 if (pr->m_bRtIsActive) {
8017 pSelectedActiveRoute = pr;
8018 pFoundActiveRoutePoint = prp;
8019 break;
8020 }
8021 }
8022
8023 if (NULL == pSelectedVizRoute) {
8024 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8025 Route *pr = (Route *)proute_array->Item(ir);
8026 if (pr->IsVisible()) {
8027 pSelectedVizRoute = pr;
8028 pFoundVizRoutePoint = prp;
8029 break;
8030 }
8031 }
8032 }
8033
8034 delete proute_array;
8035 }
8036 node = node->GetNext();
8037 }
8038
8039 // Now choose the "best" selections
8040 if (pFoundActiveRoutePoint) {
8041 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8042 m_pSelectedRoute = pSelectedActiveRoute;
8043 } else if (pFoundVizRoutePoint) {
8044 m_pFoundRoutePoint = pFoundVizRoutePoint;
8045 m_pSelectedRoute = pSelectedVizRoute;
8046 } else
8047 // default is first visible point in list
8048 m_pFoundRoutePoint = pFirstVizPoint;
8049
8050 if (m_pSelectedRoute) {
8051 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8052 } else if (m_pFoundRoutePoint) {
8053 seltype |= SELTYPE_MARKPOINT;
8054 }
8055
8056 // Highlight the selected point, to verify the proper right click selection
8057 if (m_pFoundRoutePoint) {
8058 m_pFoundRoutePoint->m_bPtIsSelected = true;
8059 wxRect wp_rect;
8060 RoutePointGui(*m_pFoundRoutePoint)
8061 .CalculateDCRect(m_dc_route, this, &wp_rect);
8062 RefreshRect(wp_rect, true);
8063 }
8064 }
8065
8066 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8067 // routes But call the popup handler with identifier appropriate to the type
8068 if (pFindRouteSeg) // there is at least one select item
8069 {
8070 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8071 SelectableItemList SelList =
8072 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8073
8074 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8075 {
8076 // Choose the first visible route containing segment in the list
8077 wxSelectableItemListNode *node = SelList.GetFirst();
8078 while (node) {
8079 SelectItem *pFindSel = node->GetData();
8080
8081 Route *pr = (Route *)pFindSel->m_pData3;
8082 if (pr->IsVisible()) {
8083 m_pSelectedRoute = pr;
8084 break;
8085 }
8086 node = node->GetNext();
8087 }
8088 }
8089
8090 if (m_pSelectedRoute) {
8091 if (NULL == m_pFoundRoutePoint)
8092 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8093
8094 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8095 if (m_pSelectedRoute->m_bRtIsSelected) {
8096#ifdef ocpnUSE_GL
8097 if (g_bopengl && m_glcc) {
8098 InvalidateGL();
8099 Update();
8100 } else
8101#endif
8102 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8103 }
8104 seltype |= SELTYPE_ROUTESEGMENT;
8105 }
8106 }
8107
8108 if (pFindTrackSeg) {
8109 m_pSelectedTrack = NULL;
8110 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8111 SelectableItemList SelList =
8112 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8113
8114 // Choose the first visible track containing segment in the list
8115 wxSelectableItemListNode *node = SelList.GetFirst();
8116 while (node) {
8117 SelectItem *pFindSel = node->GetData();
8118
8119 Track *pt = (Track *)pFindSel->m_pData3;
8120 if (pt->IsVisible()) {
8121 m_pSelectedTrack = pt;
8122 break;
8123 }
8124 node = node->GetNext();
8125 }
8126 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8127 }
8128
8129 {
8130 if (pFindCurrent) {
8131 // There may be multiple current entries at the same point.
8132 // For example, there often is a current substation (with directions
8133 // specified) co-located with its master. We want to select the
8134 // substation, so that the direction will be properly indicated on the
8135 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8136 // substation)
8137 IDX_entry *pIDX_best_candidate;
8138
8139 SelectItem *pFind = NULL;
8140 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8141 SelectableItemList SelList =
8142 pSelectTC->FindSelectionList(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8143
8144 // Default is first entry
8145 wxSelectableItemListNode *node = SelList.GetFirst();
8146 pFind = node->GetData();
8147 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8148
8149 if (SelList.GetCount() > 1) {
8150 node = node->GetNext();
8151 while (node) {
8152 pFind = node->GetData();
8153 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8154 if (pIDX_candidate->IDX_type == 'c') {
8155 pIDX_best_candidate = pIDX_candidate;
8156 break;
8157 }
8158
8159 node = node->GetNext();
8160 } // while (node)
8161 } else {
8162 wxSelectableItemListNode *node = SelList.GetFirst();
8163 pFind = node->GetData();
8164 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8165 }
8166
8167 m_pIDXCandidate = pIDX_best_candidate;
8168 seltype |= SELTYPE_CURRENTPOINT;
8169 }
8170
8171 else if (pFindTide) {
8172 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8173 seltype |= SELTYPE_TIDEPOINT;
8174 }
8175 }
8176
8177 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8178
8179 return seltype;
8180}
8181
8182void ChartCanvas::CallPopupMenu(int x, int y) {
8183 last_drag.x = x;
8184 last_drag.y = y;
8185 if (m_routeState) { // creating route?
8186 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8187 return;
8188 }
8189
8191
8192 // If tide or current point is selected, then show the TC dialog immediately
8193 // without context menu
8194 if (SELTYPE_CURRENTPOINT == seltype) {
8195 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8196 Refresh(false);
8197 return;
8198 }
8199
8200 if (SELTYPE_TIDEPOINT == seltype) {
8201 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8202 Refresh(false);
8203 return;
8204 }
8205
8206 InvokeCanvasMenu(x, y, seltype);
8207
8208 // Clean up if not deleted in InvokeCanvasMenu
8209 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8210 m_pSelectedRoute->m_bRtIsSelected = false;
8211 }
8212
8213 m_pSelectedRoute = NULL;
8214
8215 if (m_pFoundRoutePoint) {
8216 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8217 m_pFoundRoutePoint->m_bPtIsSelected = false;
8218 }
8219 m_pFoundRoutePoint = NULL;
8220
8221 Refresh(true);
8222 // Refresh(false); // needed for MSW, not GTK Why??
8223}
8224
8225bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8226 // For now just bail out completely if the point clicked is not on the chart
8227 if (std::isnan(m_cursor_lat)) return false;
8228
8229 // Mouse Clicks
8230 bool ret = false; // return true if processed
8231
8232 int x, y, mx, my;
8233 event.GetPosition(&x, &y);
8234 mx = x;
8235 my = y;
8236
8237 // Calculate meaningful SelectRadius
8238 float SelectRadius;
8239 SelectRadius = g_Platform->GetSelectRadiusPix() /
8240 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8241
8243 // We start with Double Click processing. The first left click just starts a
8244 // timer and is remembered, then we actually do something if there is a
8245 // LeftDClick. If there is, the two single clicks are ignored.
8246
8247 if (event.LeftDClick() && (cursor_region == CENTER)) {
8248 m_DoubleClickTimer->Start();
8249 singleClickEventIsValid = false;
8250
8251 double zlat, zlon;
8253 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8254
8255 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8256 if (m_bShowAIS) {
8257 SelectItem *pFindAIS;
8258 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8259
8260 if (pFindAIS) {
8261 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8262 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8263 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8264 }
8265 return true;
8266 }
8267 }
8268
8269 SelectableItemList rpSelList =
8270 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8271 wxSelectableItemListNode *node = rpSelList.GetFirst();
8272 bool b_onRPtarget = false;
8273 while (node) {
8274 SelectItem *pFind = node->GetData();
8275 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8276 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8277 b_onRPtarget = true;
8278 break;
8279 }
8280 node = node->GetNext();
8281 }
8282
8283 // Double tap with selected RoutePoint or Mark
8284
8285 if (m_pRoutePointEditTarget) {
8286 if (b_onRPtarget) {
8287 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8288 return true;
8289 } else {
8290 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8291 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8292 if (g_btouch)
8293 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8294 wxRect wp_rect;
8295 RoutePointGui(*m_pRoutePointEditTarget)
8296 .CalculateDCRect(m_dc_route, this, &wp_rect);
8297 m_pRoutePointEditTarget = NULL; // cancel selection
8298 RefreshRect(wp_rect, true);
8299 return true;
8300 }
8301 } else {
8302 node = rpSelList.GetFirst();
8303 if (node) {
8304 SelectItem *pFind = node->GetData();
8305 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8306 if (frp) {
8307 wxArrayPtrVoid *proute_array =
8308 g_pRouteMan->GetRouteArrayContaining(frp);
8309
8310 // Use route array (if any) to determine actual visibility for this
8311 // point
8312 bool brp_viz = false;
8313 if (proute_array) {
8314 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8315 Route *pr = (Route *)proute_array->Item(ir);
8316 if (pr->IsVisible()) {
8317 brp_viz = true;
8318 break;
8319 }
8320 }
8321 delete proute_array;
8322 if (!brp_viz &&
8323 frp->IsShared()) // is not visible as part of route, but still
8324 // exists as a waypoint
8325 brp_viz = frp->IsVisible(); // so treat as isolated point
8326 } else
8327 brp_viz = frp->IsVisible(); // isolated point
8328
8329 if (brp_viz) {
8330 ShowMarkPropertiesDialog(frp);
8331 return true;
8332 }
8333 }
8334 }
8335 }
8336
8337 SelectItem *cursorItem;
8338 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8339
8340 if (cursorItem) {
8341 Route *pr = (Route *)cursorItem->m_pData3;
8342 if (pr->IsVisible()) {
8343 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8344 return true;
8345 }
8346 }
8347
8348 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8349
8350 if (cursorItem) {
8351 Track *pt = (Track *)cursorItem->m_pData3;
8352 if (pt->IsVisible()) {
8353 ShowTrackPropertiesDialog(pt);
8354 return true;
8355 }
8356 }
8357
8358 // Found no object to act on, so show chart info.
8359
8360 ShowObjectQueryWindow(x, y, zlat, zlon);
8361 return true;
8362 }
8363
8365 if (event.LeftDown()) {
8366 // This really should not be needed, but....
8367 // on Windows, when using wxAUIManager, sometimes the focus is lost
8368 // when clicking into another pane, e.g.the AIS target list, and then back
8369 // to this pane. Oddly, some mouse events are not lost, however. Like this
8370 // one....
8371 SetFocus();
8372
8373 last_drag.x = mx;
8374 last_drag.y = my;
8375 leftIsDown = true;
8376
8377 if (!g_btouch) {
8378 if (m_routeState) // creating route?
8379 {
8380 double rlat, rlon;
8381 bool appending = false;
8382 bool inserting = false;
8383 Route *tail = 0;
8384
8385 SetCursor(*pCursorPencil);
8386 rlat = m_cursor_lat;
8387 rlon = m_cursor_lon;
8388
8389 m_bRouteEditing = true;
8390
8391 if (m_routeState == 1) {
8392 m_pMouseRoute = new Route();
8393 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8394 pRouteList->Append(m_pMouseRoute);
8395 r_rband.x = x;
8396 r_rband.y = y;
8397 }
8398
8399 // Check to see if there is a nearby point which may be reused
8400 RoutePoint *pMousePoint = NULL;
8401
8402 // Calculate meaningful SelectRadius
8403 double nearby_radius_meters =
8404 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8405
8406 RoutePoint *pNearbyPoint =
8407 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8408 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8409 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8410 wxArrayPtrVoid *proute_array =
8411 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8412
8413 // Use route array (if any) to determine actual visibility for this
8414 // point
8415 bool brp_viz = false;
8416 if (proute_array) {
8417 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8418 Route *pr = (Route *)proute_array->Item(ir);
8419 if (pr->IsVisible()) {
8420 brp_viz = true;
8421 break;
8422 }
8423 }
8424 delete proute_array;
8425 if (!brp_viz &&
8426 pNearbyPoint->IsShared()) // is not visible as part of route,
8427 // but still exists as a waypoint
8428 brp_viz =
8429 pNearbyPoint->IsVisible(); // so treat as isolated point
8430 } else
8431 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8432
8433 if (brp_viz) {
8434 wxString msg = _("Use nearby waypoint?");
8435 // Don't add a mark without name to the route. Name it if needed
8436 const bool noname(pNearbyPoint->GetName() == "");
8437 if (noname) {
8438 msg =
8439 _("Use nearby nameless waypoint and name it M with"
8440 " a unique number?");
8441 }
8442 // Avoid route finish on focus change for message dialog
8443 m_FinishRouteOnKillFocus = false;
8444 int dlg_return =
8445 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8446 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8447 m_FinishRouteOnKillFocus = true;
8448 if (dlg_return == wxID_YES) {
8449 if (noname) {
8450 if (m_pMouseRoute) {
8451 int last_wp_num = m_pMouseRoute->GetnPoints();
8452 // AP-ECRMB will truncate to 6 characters
8453 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8454 wxString wp_name = wxString::Format(
8455 "M%002i-%s", last_wp_num + 1, guid_short);
8456 pNearbyPoint->SetName(wp_name);
8457 } else
8458 pNearbyPoint->SetName("WPXX");
8459 }
8460 pMousePoint = pNearbyPoint;
8461
8462 // Using existing waypoint, so nothing to delete for undo.
8463 if (m_routeState > 1)
8464 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8465 Undo_HasParent, NULL);
8466
8467 tail =
8468 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8469 bool procede = false;
8470 if (tail) {
8471 procede = true;
8472 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8473 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8474 procede = false;
8475 }
8476
8477 if (procede) {
8478 int dlg_return;
8479 m_FinishRouteOnKillFocus = false;
8480 if (m_routeState ==
8481 1) { // first point in new route, preceeding route to be
8482 // added? Not touch case
8483
8484 wxString dmsg =
8485 _("Insert first part of this route in the new route?");
8486 if (tail->GetIndexOf(pMousePoint) ==
8487 tail->GetnPoints()) // Starting on last point of another
8488 // route?
8489 dmsg = _("Insert this route in the new route?");
8490
8491 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8492 dlg_return = OCPNMessageBox(
8493 this, dmsg, _("OpenCPN Route Create"),
8494 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8495 m_FinishRouteOnKillFocus = true;
8496
8497 if (dlg_return == wxID_YES) {
8498 inserting = true; // part of the other route will be
8499 // preceeding the new route
8500 }
8501 }
8502 } else {
8503 wxString dmsg =
8504 _("Append last part of this route to the new route?");
8505 if (tail->GetIndexOf(pMousePoint) == 1)
8506 dmsg = _(
8507 "Append this route to the new route?"); // Picking the
8508 // first point
8509 // of another
8510 // route?
8511
8512 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8513 dlg_return = OCPNMessageBox(
8514 this, dmsg, _("OpenCPN Route Create"),
8515 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8516 m_FinishRouteOnKillFocus = true;
8517
8518 if (dlg_return == wxID_YES) {
8519 appending = true; // part of the other route will be
8520 // appended to the new route
8521 }
8522 }
8523 }
8524 }
8525
8526 // check all other routes to see if this point appears in any
8527 // other route If it appears in NO other route, then it should e
8528 // considered an isolated mark
8529 if (!FindRouteContainingWaypoint(pMousePoint))
8530 pMousePoint->SetShared(true);
8531 }
8532 }
8533 }
8534
8535 if (NULL == pMousePoint) { // need a new point
8536 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8537 "", wxEmptyString);
8538 pMousePoint->SetNameShown(false);
8539
8540 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8541
8542 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8543
8544 if (m_routeState > 1)
8545 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8546 Undo_IsOrphanded, NULL);
8547 }
8548
8549 if (m_pMouseRoute) {
8550 if (m_routeState == 1) {
8551 // First point in the route.
8552 m_pMouseRoute->AddPoint(pMousePoint);
8553 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8554 } else {
8555 if (m_pMouseRoute->m_NextLegGreatCircle) {
8556 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8557 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8558 &rhumbBearing, &rhumbDist);
8559 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8560 rlat, &gcDist, &gcBearing, NULL);
8561 double gcDistNM = gcDist / 1852.0;
8562
8563 // Empirically found expression to get reasonable route segments.
8564 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8565 pow(rhumbDist - gcDistNM - 1, 0.5);
8566
8567 wxString msg;
8568 msg << _("For this leg the Great Circle route is ")
8569 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8570 << _(" shorter than rhumbline.\n\n")
8571 << _("Would you like include the Great Circle routing points "
8572 "for this leg?");
8573
8574 m_FinishRouteOnKillFocus = false;
8575 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8576 // does not fully capture mouse
8577
8578 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8579 wxYES_NO | wxNO_DEFAULT);
8580
8581 m_disable_edge_pan = false;
8582 m_FinishRouteOnKillFocus = true;
8583
8584 if (answer == wxID_YES) {
8585 RoutePoint *gcPoint;
8586 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8587 wxRealPoint gcCoord;
8588
8589 for (int i = 1; i <= segmentCount; i++) {
8590 double fraction = (double)i * (1.0 / (double)segmentCount);
8591 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8592 gcDist * fraction, gcBearing,
8593 &gcCoord.x, &gcCoord.y, NULL);
8594
8595 if (i < segmentCount) {
8596 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8597 wxEmptyString);
8598 gcPoint->SetNameShown(false);
8599 // pConfig->AddNewWayPoint(gcPoint, -1);
8600 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8601
8602 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8603 gcPoint);
8604 } else {
8605 gcPoint = pMousePoint; // Last point, previously exsisting!
8606 }
8607
8608 m_pMouseRoute->AddPoint(gcPoint);
8609 pSelect->AddSelectableRouteSegment(
8610 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8611 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8612 prevGcPoint = gcPoint;
8613 }
8614
8615 undo->CancelUndoableAction(true);
8616
8617 } else {
8618 m_pMouseRoute->AddPoint(pMousePoint);
8619 pSelect->AddSelectableRouteSegment(
8620 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8621 pMousePoint, m_pMouseRoute);
8622 undo->AfterUndoableAction(m_pMouseRoute);
8623 }
8624 } else {
8625 // Ordinary rhumblinesegment.
8626 m_pMouseRoute->AddPoint(pMousePoint);
8627 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8628 rlon, m_prev_pMousePoint,
8629 pMousePoint, m_pMouseRoute);
8630 undo->AfterUndoableAction(m_pMouseRoute);
8631 }
8632 }
8633 }
8634 m_prev_rlat = rlat;
8635 m_prev_rlon = rlon;
8636 m_prev_pMousePoint = pMousePoint;
8637 if (m_pMouseRoute)
8638 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8639
8640 m_routeState++;
8641
8642 if (appending ||
8643 inserting) { // Appending a route or making a new route
8644 int connect = tail->GetIndexOf(pMousePoint);
8645 if (connect == 1) {
8646 inserting = false; // there is nothing to insert
8647 appending = true; // so append
8648 }
8649 int length = tail->GetnPoints();
8650
8651 int i;
8652 int start, stop;
8653 if (appending) {
8654 start = connect + 1;
8655 stop = length;
8656 } else { // inserting
8657 start = 1;
8658 stop = connect;
8659 m_pMouseRoute->RemovePoint(
8660 m_pMouseRoute
8661 ->GetLastPoint()); // Remove the first and only point
8662 }
8663 for (i = start; i <= stop; i++) {
8664 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8665 if (m_pMouseRoute)
8666 m_pMouseRoute->m_lastMousePointIndex =
8667 m_pMouseRoute->GetnPoints();
8668 m_routeState++;
8669 gFrame->RefreshAllCanvas();
8670 ret = true;
8671 }
8672 m_prev_rlat =
8673 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8674 m_prev_rlon =
8675 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8676 m_pMouseRoute->FinalizeForRendering();
8677 }
8678 gFrame->RefreshAllCanvas();
8679 ret = true;
8680 }
8681
8682 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8683 {
8684 SetCursor(*pCursorPencil);
8685
8686 if (!m_pMeasureRoute) {
8687 m_pMeasureRoute = new Route();
8688 pRouteList->Append(m_pMeasureRoute);
8689 }
8690
8691 if (m_nMeasureState == 1) {
8692 r_rband.x = x;
8693 r_rband.y = y;
8694 }
8695
8696 RoutePoint *pMousePoint =
8697 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8698 wxEmptyString, wxEmptyString);
8699 pMousePoint->m_bShowName = false;
8700 pMousePoint->SetShowWaypointRangeRings(false);
8701
8702 m_pMeasureRoute->AddPoint(pMousePoint);
8703
8704 m_prev_rlat = m_cursor_lat;
8705 m_prev_rlon = m_cursor_lon;
8706 m_prev_pMousePoint = pMousePoint;
8707 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8708
8709 m_nMeasureState++;
8710 gFrame->RefreshAllCanvas();
8711 ret = true;
8712 }
8713
8714 else {
8715 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8716 }
8717 } // !g_btouch
8718 else { // g_btouch
8719
8720 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8721 // if near screen edge, pan with injection
8722 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8723 // return;
8724 // }
8725 }
8726 }
8727
8728 if (ret) return true;
8729 }
8730
8731 if (event.Dragging()) {
8732 // in touch screen mode ensure the finger/cursor is on the selected point's
8733 // radius to allow dragging
8734 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8735 if (g_btouch) {
8736 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8737 SelectItem *pFind = NULL;
8738 SelectableItemList SelList = pSelect->FindSelectionList(
8739 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8740 wxSelectableItemListNode *node = SelList.GetFirst();
8741 while (node) {
8742 pFind = node->GetData();
8743 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8744 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8745 node = node->GetNext();
8746 }
8747 }
8748
8749 // Check for use of dragHandle
8750 if (m_pRoutePointEditTarget &&
8751 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8752 SelectItem *pFind = NULL;
8753 SelectableItemList SelList = pSelect->FindSelectionList(
8754 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8755 wxSelectableItemListNode *node = SelList.GetFirst();
8756 while (node) {
8757 pFind = node->GetData();
8758 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8759 if (m_pRoutePointEditTarget == frp) {
8760 m_bIsInRadius = true;
8761 break;
8762 }
8763 node = node->GetNext();
8764 }
8765
8766 if (!m_dragoffsetSet) {
8767 RoutePointGui(*m_pRoutePointEditTarget)
8768 .PresetDragOffset(this, mouse_x, mouse_y);
8769 m_dragoffsetSet = true;
8770 }
8771 }
8772 }
8773
8774 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8775 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8776
8777 if (NULL == g_pMarkInfoDialog) {
8778 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8779 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8780 DraggingAllowed = false;
8781
8782 if (m_pRoutePointEditTarget &&
8783 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8784 DraggingAllowed = false;
8785
8786 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8787
8788 if (DraggingAllowed) {
8789 if (!undo->InUndoableAction()) {
8790 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8791 Undo_NeedsCopy, m_pFoundPoint);
8792 }
8793
8794 // Get the update rectangle for the union of the un-edited routes
8795 wxRect pre_rect;
8796
8797 if (!g_bopengl && m_pEditRouteArray) {
8798 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8799 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8800 // Need to validate route pointer
8801 // Route may be gone due to drgging close to ownship with
8802 // "Delete On Arrival" state set, as in the case of
8803 // navigating to an isolated waypoint on a temporary route
8804 if (g_pRouteMan->IsRouteValid(pr)) {
8805 wxRect route_rect;
8806 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8807 pre_rect.Union(route_rect);
8808 }
8809 }
8810 }
8811
8812 double new_cursor_lat = m_cursor_lat;
8813 double new_cursor_lon = m_cursor_lon;
8814
8815 if (CheckEdgePan(x, y, true, 5, 2))
8816 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8817
8818 // update the point itself
8819 if (g_btouch) {
8820 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8821 // new_cursor_lat, new_cursor_lon);
8822 RoutePointGui(*m_pRoutePointEditTarget)
8823 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8824 // update the Drag Handle entry in the pSelect list
8825 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8826 m_pRoutePointEditTarget,
8827 SELTYPE_DRAGHANDLE);
8828 m_pFoundPoint->m_slat =
8829 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8830 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8831 } else {
8832 m_pRoutePointEditTarget->m_lat =
8833 new_cursor_lat; // update the RoutePoint entry
8834 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8835 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8836 m_pFoundPoint->m_slat =
8837 new_cursor_lat; // update the SelectList entry
8838 m_pFoundPoint->m_slon = new_cursor_lon;
8839 }
8840
8841 // Update the MarkProperties Dialog, if currently shown
8842 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8843 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8844 g_pMarkInfoDialog->UpdateProperties(true);
8845 }
8846
8847 if (g_bopengl) {
8848 // InvalidateGL();
8849 Refresh(false);
8850 } else {
8851 // Get the update rectangle for the edited route
8852 wxRect post_rect;
8853
8854 if (m_pEditRouteArray) {
8855 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8856 ir++) {
8857 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8858 if (g_pRouteMan->IsRouteValid(pr)) {
8859 wxRect route_rect;
8860 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8861 post_rect.Union(route_rect);
8862 }
8863 }
8864 }
8865
8866 // Invalidate the union region
8867 pre_rect.Union(post_rect);
8868 RefreshRect(pre_rect, false);
8869 }
8870 gFrame->RefreshCanvasOther(this);
8871 m_bRoutePoinDragging = true;
8872 }
8873 ret = true;
8874 } // if Route Editing
8875
8876 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8877 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8878
8879 if (NULL == g_pMarkInfoDialog) {
8880 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8881 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8882 DraggingAllowed = false;
8883
8884 if (m_pRoutePointEditTarget &&
8885 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8886 DraggingAllowed = false;
8887
8888 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8889
8890 if (DraggingAllowed) {
8891 if (!undo->InUndoableAction()) {
8892 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8893 Undo_NeedsCopy, m_pFoundPoint);
8894 }
8895
8896 // The mark may be an anchorwatch
8897 double lpp1 = 0.;
8898 double lpp2 = 0.;
8899 double lppmax;
8900
8901 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8902 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8903 }
8904 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8905 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8906 }
8907 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8908
8909 // Get the update rectangle for the un-edited mark
8910 wxRect pre_rect;
8911 if (!g_bopengl) {
8912 RoutePointGui(*m_pRoutePointEditTarget)
8913 .CalculateDCRect(m_dc_route, this, &pre_rect);
8914 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8915 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8916 (int)(lppmax - (pre_rect.height / 2)));
8917 }
8918
8919 // update the point itself
8920 if (g_btouch) {
8921 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8922 // m_cursor_lat, m_cursor_lon);
8923 RoutePointGui(*m_pRoutePointEditTarget)
8924 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8925 // update the Drag Handle entry in the pSelect list
8926 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
8927 m_pRoutePointEditTarget,
8928 SELTYPE_DRAGHANDLE);
8929 m_pFoundPoint->m_slat =
8930 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8931 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8932 } else {
8933 m_pRoutePointEditTarget->m_lat =
8934 m_cursor_lat; // update the RoutePoint entry
8935 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
8936 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8937 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
8938 m_pFoundPoint->m_slon = m_cursor_lon;
8939 }
8940
8941 // Update the MarkProperties Dialog, if currently shown
8942 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8943 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8944 g_pMarkInfoDialog->UpdateProperties(true);
8945 }
8946
8947 // Invalidate the union region
8948 if (g_bopengl) {
8949 if (!g_btouch) InvalidateGL();
8950 Refresh(false);
8951 } else {
8952 // Get the update rectangle for the edited mark
8953 wxRect post_rect;
8954 RoutePointGui(*m_pRoutePointEditTarget)
8955 .CalculateDCRect(m_dc_route, this, &post_rect);
8956 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
8957 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
8958 (int)(lppmax - (post_rect.height / 2)));
8959
8960 // Invalidate the union region
8961 pre_rect.Union(post_rect);
8962 RefreshRect(pre_rect, false);
8963 }
8964 gFrame->RefreshCanvasOther(this);
8965 m_bRoutePoinDragging = true;
8966 }
8967 ret = true;
8968 }
8969
8970 if (ret) return true;
8971 } // dragging
8972
8973 if (event.LeftUp()) {
8974 bool b_startedit_route = false;
8975 m_dragoffsetSet = false;
8976
8977 if (g_btouch) {
8978 m_bChartDragging = false;
8979 m_bIsInRadius = false;
8980
8981 if (m_routeState) // creating route?
8982 {
8983 if (m_bedge_pan) {
8984 m_bedge_pan = false;
8985 return false;
8986 }
8987
8988 double rlat, rlon;
8989 bool appending = false;
8990 bool inserting = false;
8991 Route *tail = 0;
8992
8993 rlat = m_cursor_lat;
8994 rlon = m_cursor_lon;
8995
8996 if (m_pRoutePointEditTarget) {
8997 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8998 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8999 if (!g_bopengl) {
9000 wxRect wp_rect;
9001 RoutePointGui(*m_pRoutePointEditTarget)
9002 .CalculateDCRect(m_dc_route, this, &wp_rect);
9003 RefreshRect(wp_rect, true);
9004 }
9005 m_pRoutePointEditTarget = NULL;
9006 }
9007 m_bRouteEditing = true;
9008
9009 if (m_routeState == 1) {
9010 m_pMouseRoute = new Route();
9011 m_pMouseRoute->SetHiLite(50);
9012 pRouteList->Append(m_pMouseRoute);
9013 r_rband.x = x;
9014 r_rband.y = y;
9015 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9016 }
9017
9018 // Check to see if there is a nearby point which may be reused
9019 RoutePoint *pMousePoint = NULL;
9020
9021 // Calculate meaningful SelectRadius
9022 double nearby_radius_meters =
9023 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9024
9025 RoutePoint *pNearbyPoint =
9026 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9027 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9028 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9029 int dlg_return;
9030#ifndef __WXOSX__
9031 m_FinishRouteOnKillFocus =
9032 false; // Avoid route finish on focus change for message dialog
9033 dlg_return = OCPNMessageBox(
9034 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9035 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9036 m_FinishRouteOnKillFocus = true;
9037#else
9038 dlg_return = wxID_YES;
9039#endif
9040 if (dlg_return == wxID_YES) {
9041 pMousePoint = pNearbyPoint;
9042
9043 // Using existing waypoint, so nothing to delete for undo.
9044 if (m_routeState > 1)
9045 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9046 Undo_HasParent, NULL);
9047 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9048
9049 bool procede = false;
9050 if (tail) {
9051 procede = true;
9052 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9053 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9054 procede = false;
9055 }
9056
9057 if (procede) {
9058 int dlg_return;
9059 m_FinishRouteOnKillFocus = false;
9060 if (m_routeState == 1) { // first point in new route, preceeding
9061 // route to be added? touch case
9062
9063 wxString dmsg =
9064 _("Insert first part of this route in the new route?");
9065 if (tail->GetIndexOf(pMousePoint) ==
9066 tail->GetnPoints()) // Starting on last point of another
9067 // route?
9068 dmsg = _("Insert this route in the new route?");
9069
9070 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9071 dlg_return =
9072 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9073 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9074 m_FinishRouteOnKillFocus = true;
9075
9076 if (dlg_return == wxID_YES) {
9077 inserting = true; // part of the other route will be
9078 // preceeding the new route
9079 }
9080 }
9081 } else {
9082 wxString dmsg =
9083 _("Append last part of this route to the new route?");
9084 if (tail->GetIndexOf(pMousePoint) == 1)
9085 dmsg = _(
9086 "Append this route to the new route?"); // Picking the
9087 // first point of
9088 // another route?
9089
9090 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9091 dlg_return =
9092 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9093 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9094 m_FinishRouteOnKillFocus = true;
9095
9096 if (dlg_return == wxID_YES) {
9097 appending = true; // part of the other route will be
9098 // appended to the new route
9099 }
9100 }
9101 }
9102 }
9103
9104 // check all other routes to see if this point appears in any other
9105 // route If it appears in NO other route, then it should e
9106 // considered an isolated mark
9107 if (!FindRouteContainingWaypoint(pMousePoint))
9108 pMousePoint->SetShared(true);
9109 }
9110 }
9111
9112 if (NULL == pMousePoint) { // need a new point
9113 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9114 "", wxEmptyString);
9115 pMousePoint->SetNameShown(false);
9116
9117 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9118
9119 if (m_routeState > 1)
9120 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9121 Undo_IsOrphanded, NULL);
9122 }
9123
9124 if (m_routeState == 1) {
9125 // First point in the route.
9126 m_pMouseRoute->AddPoint(pMousePoint);
9127 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9128
9129 } else {
9130 if (m_pMouseRoute->m_NextLegGreatCircle) {
9131 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9132 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9133 &rhumbBearing, &rhumbDist);
9134 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9135 &gcDist, &gcBearing, NULL);
9136 double gcDistNM = gcDist / 1852.0;
9137
9138 // Empirically found expression to get reasonable route segments.
9139 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9140 pow(rhumbDist - gcDistNM - 1, 0.5);
9141
9142 wxString msg;
9143 msg << _("For this leg the Great Circle route is ")
9144 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9145 << _(" shorter than rhumbline.\n\n")
9146 << _("Would you like include the Great Circle routing points "
9147 "for this leg?");
9148
9149#ifndef __WXOSX__
9150 m_FinishRouteOnKillFocus = false;
9151 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9152 wxYES_NO | wxNO_DEFAULT);
9153 m_FinishRouteOnKillFocus = true;
9154#else
9155 int answer = wxID_NO;
9156#endif
9157
9158 if (answer == wxID_YES) {
9159 RoutePoint *gcPoint;
9160 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9161 wxRealPoint gcCoord;
9162
9163 for (int i = 1; i <= segmentCount; i++) {
9164 double fraction = (double)i * (1.0 / (double)segmentCount);
9165 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9166 gcDist * fraction, gcBearing,
9167 &gcCoord.x, &gcCoord.y, NULL);
9168
9169 if (i < segmentCount) {
9170 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9171 wxEmptyString);
9172 gcPoint->SetNameShown(false);
9173 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9174 gcPoint);
9175 } else {
9176 gcPoint = pMousePoint; // Last point, previously exsisting!
9177 }
9178
9179 m_pMouseRoute->AddPoint(gcPoint);
9180 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9181
9182 pSelect->AddSelectableRouteSegment(
9183 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9184 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9185 prevGcPoint = gcPoint;
9186 }
9187
9188 undo->CancelUndoableAction(true);
9189
9190 } else {
9191 m_pMouseRoute->AddPoint(pMousePoint);
9192 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9193 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9194 rlon, m_prev_pMousePoint,
9195 pMousePoint, m_pMouseRoute);
9196 undo->AfterUndoableAction(m_pMouseRoute);
9197 }
9198 } else {
9199 // Ordinary rhumblinesegment.
9200 m_pMouseRoute->AddPoint(pMousePoint);
9201 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9202
9203 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9204 rlon, m_prev_pMousePoint,
9205 pMousePoint, m_pMouseRoute);
9206 undo->AfterUndoableAction(m_pMouseRoute);
9207 }
9208 }
9209
9210 m_prev_rlat = rlat;
9211 m_prev_rlon = rlon;
9212 m_prev_pMousePoint = pMousePoint;
9213 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9214
9215 m_routeState++;
9216
9217 if (appending ||
9218 inserting) { // Appending a route or making a new route
9219 int connect = tail->GetIndexOf(pMousePoint);
9220 if (connect == 1) {
9221 inserting = false; // there is nothing to insert
9222 appending = true; // so append
9223 }
9224 int length = tail->GetnPoints();
9225
9226 int i;
9227 int start, stop;
9228 if (appending) {
9229 start = connect + 1;
9230 stop = length;
9231 } else { // inserting
9232 start = 1;
9233 stop = connect;
9234 m_pMouseRoute->RemovePoint(
9235 m_pMouseRoute
9236 ->GetLastPoint()); // Remove the first and only point
9237 }
9238 for (i = start; i <= stop; i++) {
9239 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9240 if (m_pMouseRoute)
9241 m_pMouseRoute->m_lastMousePointIndex =
9242 m_pMouseRoute->GetnPoints();
9243 m_routeState++;
9244 gFrame->RefreshAllCanvas();
9245 ret = true;
9246 }
9247 m_prev_rlat =
9248 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9249 m_prev_rlon =
9250 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9251 m_pMouseRoute->FinalizeForRendering();
9252 }
9253
9254 Refresh(true);
9255 ret = true;
9256 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9257 {
9258 if (m_bedge_pan) {
9259 m_bedge_pan = false;
9260 return false;
9261 }
9262
9263 if (m_nMeasureState == 1) {
9264 m_pMeasureRoute = new Route();
9265 pRouteList->Append(m_pMeasureRoute);
9266 r_rband.x = x;
9267 r_rband.y = y;
9268 }
9269
9270 if (m_pMeasureRoute) {
9271 RoutePoint *pMousePoint =
9272 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9273 wxEmptyString, wxEmptyString);
9274 pMousePoint->m_bShowName = false;
9275
9276 m_pMeasureRoute->AddPoint(pMousePoint);
9277
9278 m_prev_rlat = m_cursor_lat;
9279 m_prev_rlon = m_cursor_lon;
9280 m_prev_pMousePoint = pMousePoint;
9281 m_pMeasureRoute->m_lastMousePointIndex =
9282 m_pMeasureRoute->GetnPoints();
9283
9284 m_nMeasureState++;
9285 } else {
9286 CancelMeasureRoute();
9287 }
9288
9289 Refresh(true);
9290 ret = true;
9291 } else {
9292 bool bSelectAllowed = true;
9293 if (NULL == g_pMarkInfoDialog) {
9294 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9295 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9296 bSelectAllowed = false;
9297
9298 /*if this left up happens at the end of a route point dragging and if
9299 the cursor/thumb is on the draghandle icon, not on the point iself a new
9300 selection will select nothing and the drag will never be ended, so the
9301 legs around this point never selectable. At this step we don't need a
9302 new selection, just keep the previoulsly selected and dragged point */
9303 if (m_bRoutePoinDragging) bSelectAllowed = false;
9304
9305 if (bSelectAllowed) {
9306 bool b_was_editing_mark = m_bMarkEditing;
9307 bool b_was_editing_route = m_bRouteEditing;
9308 FindRoutePointsAtCursor(SelectRadius,
9309 true); // Possibly selecting a point in a
9310 // route for later dragging
9311
9312 /*route and a mark points in layer can't be dragged so should't be
9313 * selected and no draghandle icon*/
9314 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9315 m_pRoutePointEditTarget = NULL;
9316
9317 if (!b_was_editing_route) {
9318 if (m_pEditRouteArray) {
9319 b_startedit_route = true;
9320
9321 // Hide the track and route rollover during route point edit, not
9322 // needed, and may be confusing
9323 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9324 m_pTrackRolloverWin->IsActive(false);
9325 }
9326 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9327 m_pRouteRolloverWin->IsActive(false);
9328 }
9329
9330 wxRect pre_rect;
9331 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9332 ir++) {
9333 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9334 // Need to validate route pointer
9335 // Route may be gone due to drgging close to ownship with
9336 // "Delete On Arrival" state set, as in the case of
9337 // navigating to an isolated waypoint on a temporary route
9338 if (g_pRouteMan->IsRouteValid(pr)) {
9339 // pr->SetHiLite(50);
9340 wxRect route_rect;
9341 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9342 pre_rect.Union(route_rect);
9343 }
9344 }
9345 RefreshRect(pre_rect, true);
9346 }
9347 } else {
9348 b_startedit_route = false;
9349 }
9350
9351 // Mark editing
9352 if (m_pRoutePointEditTarget) {
9353 if (b_was_editing_mark ||
9354 b_was_editing_route) { // kill previous hilight
9355 if (m_lastRoutePointEditTarget) {
9356 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9357 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9358 RoutePointGui(*m_lastRoutePointEditTarget)
9359 .EnableDragHandle(false);
9360 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9361 SELTYPE_DRAGHANDLE);
9362 }
9363 }
9364
9365 if (m_pRoutePointEditTarget) {
9366 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9367 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9368 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9369 wxPoint2DDouble dragHandlePoint =
9370 RoutePointGui(*m_pRoutePointEditTarget)
9371 .GetDragHandlePoint(this);
9372 pSelect->AddSelectablePoint(
9373 dragHandlePoint.m_y, dragHandlePoint.m_x,
9374 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9375 }
9376 } else { // Deselect everything
9377 if (m_lastRoutePointEditTarget) {
9378 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9379 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9380 RoutePointGui(*m_lastRoutePointEditTarget)
9381 .EnableDragHandle(false);
9382 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9383 SELTYPE_DRAGHANDLE);
9384
9385 // Clear any routes being edited, probably orphans
9386 wxArrayPtrVoid *lastEditRouteArray =
9387 g_pRouteMan->GetRouteArrayContaining(
9388 m_lastRoutePointEditTarget);
9389 if (lastEditRouteArray) {
9390 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9391 ir++) {
9392 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9393 if (g_pRouteMan->IsRouteValid(pr)) {
9394 pr->m_bIsBeingEdited = false;
9395 }
9396 }
9397 delete lastEditRouteArray;
9398 }
9399 }
9400 }
9401
9402 // Do the refresh
9403
9404 if (g_bopengl) {
9405 InvalidateGL();
9406 Refresh(false);
9407 } else {
9408 if (m_lastRoutePointEditTarget) {
9409 wxRect wp_rect;
9410 RoutePointGui(*m_lastRoutePointEditTarget)
9411 .CalculateDCRect(m_dc_route, this, &wp_rect);
9412 RefreshRect(wp_rect, true);
9413 }
9414
9415 if (m_pRoutePointEditTarget) {
9416 wxRect wp_rect;
9417 RoutePointGui(*m_pRoutePointEditTarget)
9418 .CalculateDCRect(m_dc_route, this, &wp_rect);
9419 RefreshRect(wp_rect, true);
9420 }
9421 }
9422 }
9423 } // bSelectAllowed
9424
9425 // Check to see if there is a route or AIS target under the cursor
9426 // If so, start the rollover timer which creates the popup
9427 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9428 bool b_start_rollover = false;
9429 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9430 SelectItem *pFind = pSelectAIS->FindSelection(
9431 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9432 if (pFind) b_start_rollover = true;
9433 }
9434
9435 if (!b_start_rollover && !b_startedit_route) {
9436 SelectableItemList SelList = pSelect->FindSelectionList(
9437 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9438 wxSelectableItemListNode *node = SelList.GetFirst();
9439 while (node) {
9440 SelectItem *pFindSel = node->GetData();
9441
9442 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9443
9444 if (pr && pr->IsVisible()) {
9445 b_start_rollover = true;
9446 break;
9447 }
9448 node = node->GetNext();
9449 } // while
9450 }
9451
9452 if (!b_start_rollover && !b_startedit_route) {
9453 SelectableItemList SelList = pSelect->FindSelectionList(
9454 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9455 wxSelectableItemListNode *node = SelList.GetFirst();
9456 while (node) {
9457 SelectItem *pFindSel = node->GetData();
9458
9459 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9460
9461 if (tr && tr->IsVisible()) {
9462 b_start_rollover = true;
9463 break;
9464 }
9465 node = node->GetNext();
9466 } // while
9467 }
9468
9469 if (b_start_rollover)
9470 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9471 wxTIMER_ONE_SHOT);
9472 Route *tail = 0;
9473 Route *current = 0;
9474 bool appending = false;
9475 bool inserting = false;
9476 int connect = 0;
9477 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9478 // drag
9479 if (m_pRoutePointEditTarget) {
9480 // Check to see if there is a nearby point which may replace the
9481 // dragged one
9482 RoutePoint *pMousePoint = NULL;
9483
9484 int index_last;
9485 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9486 double nearby_radius_meters =
9487 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9488 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9489 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9490 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9491 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9492 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9493 bool duplicate =
9494 false; // ensure we won't create duplicate point in routes
9495 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9496 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9497 ir++) {
9498 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9499 if (pr && pr->pRoutePointList) {
9500 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9501 wxNOT_FOUND) {
9502 duplicate = true;
9503 break;
9504 }
9505 }
9506 }
9507 }
9508
9509 // Special case:
9510 // Allow "re-use" of a route's waypoints iff it is a simple
9511 // isolated route. This allows, for instance, creation of a closed
9512 // polygon route
9513 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9514
9515 if (!duplicate) {
9516 int dlg_return;
9517 dlg_return =
9518 OCPNMessageBox(this,
9519 _("Replace this RoutePoint by the nearby "
9520 "Waypoint?"),
9521 _("OpenCPN RoutePoint change"),
9522 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9523 if (dlg_return == wxID_YES) {
9524 /*double confirmation if the dragged point has been manually
9525 * created which can be important and could be deleted
9526 * unintentionally*/
9527
9528 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9529 pNearbyPoint);
9530 current =
9531 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9532
9533 if (tail && current && (tail != current)) {
9534 int dlg_return1;
9535 connect = tail->GetIndexOf(pNearbyPoint);
9536 int index_current_route =
9537 current->GetIndexOf(m_pRoutePointEditTarget);
9538 index_last = current->GetIndexOf(current->GetLastPoint());
9539 dlg_return1 = wxID_NO;
9540 if (index_last ==
9541 index_current_route) { // we are dragging the last
9542 // point of the route
9543 if (connect != tail->GetnPoints()) { // anything to do?
9544
9545 wxString dmsg(
9546 _("Last part of route to be appended to dragged "
9547 "route?"));
9548 if (connect == 1)
9549 dmsg =
9550 _("Full route to be appended to dragged route?");
9551
9552 dlg_return1 = OCPNMessageBox(
9553 this, dmsg, _("OpenCPN Route Create"),
9554 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9555 if (dlg_return1 == wxID_YES) {
9556 appending = true;
9557 }
9558 }
9559 } else if (index_current_route ==
9560 1) { // dragging the first point of the route
9561 if (connect != 1) { // anything to do?
9562
9563 wxString dmsg(
9564 _("First part of route to be inserted into dragged "
9565 "route?"));
9566 if (connect == tail->GetnPoints())
9567 dmsg = _(
9568 "Full route to be inserted into dragged route?");
9569
9570 dlg_return1 = OCPNMessageBox(
9571 this, dmsg, _("OpenCPN Route Create"),
9572 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9573 if (dlg_return1 == wxID_YES) {
9574 inserting = true;
9575 }
9576 }
9577 }
9578 }
9579
9580 if (m_pRoutePointEditTarget->IsShared()) {
9581 // dlg_return = wxID_NO;
9582 dlg_return = OCPNMessageBox(
9583 this,
9584 _("Do you really want to delete and replace this "
9585 "WayPoint") +
9586 "\n" + _("which has been created manually?"),
9587 ("OpenCPN RoutePoint warning"),
9588 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9589 }
9590 }
9591 if (dlg_return == wxID_YES) {
9592 pMousePoint = pNearbyPoint;
9593 if (pMousePoint->m_bIsolatedMark) {
9594 pMousePoint->SetShared(true);
9595 }
9596 pMousePoint->m_bIsolatedMark =
9597 false; // definitely no longer isolated
9598 pMousePoint->m_bIsInRoute = true;
9599 }
9600 }
9601 }
9602 }
9603 if (!pMousePoint)
9604 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9605
9606 if (m_pEditRouteArray) {
9607 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9608 ir++) {
9609 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9610 if (g_pRouteMan->IsRouteValid(pr)) {
9611 if (pMousePoint) { // remove the dragged point and insert the
9612 // nearby
9613 int nRP =
9614 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9615
9616 pSelect->DeleteAllSelectableRoutePoints(pr);
9617 pSelect->DeleteAllSelectableRouteSegments(pr);
9618
9619 pr->pRoutePointList->Insert(nRP, pMousePoint);
9620 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9621
9622 pSelect->AddAllSelectableRouteSegments(pr);
9623 pSelect->AddAllSelectableRoutePoints(pr);
9624 }
9625 pr->FinalizeForRendering();
9626 pr->UpdateSegmentDistances();
9627 if (m_bRoutePoinDragging) {
9628 // pConfig->UpdateRoute(pr);
9629 NavObj_dB::GetInstance().UpdateRoute(pr);
9630 }
9631 }
9632 }
9633 }
9634
9635 // Update the RouteProperties Dialog, if currently shown
9636 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9637 if (m_pEditRouteArray) {
9638 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9639 ir++) {
9640 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9641 if (g_pRouteMan->IsRouteValid(pr)) {
9642 if (pRoutePropDialog->GetRoute() == pr) {
9643 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9644 }
9645 /* cannot edit track points anyway
9646 else if ( ( NULL !=
9647 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9648 pTrackPropDialog->m_pTrack == pr ) {
9649 pTrackPropDialog->SetTrackAndUpdate(
9650 pr );
9651 }
9652 */
9653 }
9654 }
9655 }
9656 }
9657 if (pMousePoint) { // clear all about the dragged point
9658 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9659 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9660 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9661 // Hide mark properties dialog if open on the replaced point
9662 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9663 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9664 g_pMarkInfoDialog->Hide();
9665
9666 delete m_pRoutePointEditTarget;
9667 m_lastRoutePointEditTarget = NULL;
9668 m_pRoutePointEditTarget = NULL;
9669 undo->AfterUndoableAction(pMousePoint);
9670 undo->InvalidateUndo();
9671 }
9672 }
9673 }
9674
9675 else if (m_bMarkEditing) { // End of way point drag
9676 if (m_pRoutePointEditTarget)
9677 if (m_bRoutePoinDragging) {
9678 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9679 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9680 }
9681 }
9682
9683 if (m_pRoutePointEditTarget)
9684 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9685
9686 if (!m_pRoutePointEditTarget) {
9687 delete m_pEditRouteArray;
9688 m_pEditRouteArray = NULL;
9689 m_bRouteEditing = false;
9690 }
9691 m_bRoutePoinDragging = false;
9692
9693 if (appending) { // Appending to the route of which the last point is
9694 // dragged onto another route
9695
9696 // copy tail from connect until length to end of current after dragging
9697
9698 int length = tail->GetnPoints();
9699 for (int i = connect + 1; i <= length; i++) {
9700 current->AddPointAndSegment(tail->GetPoint(i), false);
9701 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9702 m_routeState++;
9703 gFrame->RefreshAllCanvas();
9704 ret = true;
9705 }
9706 current->FinalizeForRendering();
9707 current->m_bIsBeingEdited = false;
9708 FinishRoute();
9709 g_pRouteMan->DeleteRoute(tail);
9710 }
9711 if (inserting) {
9712 pSelect->DeleteAllSelectableRoutePoints(current);
9713 pSelect->DeleteAllSelectableRouteSegments(current);
9714 for (int i = 1; i < connect; i++) { // numbering in the tail route
9715 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9716 }
9717 pSelect->AddAllSelectableRouteSegments(current);
9718 pSelect->AddAllSelectableRoutePoints(current);
9719 current->FinalizeForRendering();
9720 current->m_bIsBeingEdited = false;
9721 g_pRouteMan->DeleteRoute(tail);
9722 }
9723
9724 // Update the RouteProperties Dialog, if currently shown
9725 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9726 if (m_pEditRouteArray) {
9727 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9728 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9729 if (g_pRouteMan->IsRouteValid(pr)) {
9730 if (pRoutePropDialog->GetRoute() == pr) {
9731 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9732 }
9733 }
9734 }
9735 }
9736 }
9737
9738 } // g_btouch
9739
9740 else { // !g_btouch
9741 if (m_bRouteEditing) { // End of RoutePoint drag
9742 Route *tail = 0;
9743 Route *current = 0;
9744 bool appending = false;
9745 bool inserting = false;
9746 int connect = 0;
9747 int index_last;
9748 if (m_pRoutePointEditTarget) {
9749 m_pRoutePointEditTarget->m_bBlink = false;
9750 // Check to see if there is a nearby point which may replace the
9751 // dragged one
9752 RoutePoint *pMousePoint = NULL;
9753 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9754 double nearby_radius_meters =
9755 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9756 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9757 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9758 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9759 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9760 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9761 bool duplicate = false; // don't create duplicate point in routes
9762 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9763 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9764 ir++) {
9765 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9766 if (pr && pr->pRoutePointList) {
9767 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9768 wxNOT_FOUND) {
9769 duplicate = true;
9770 break;
9771 }
9772 }
9773 }
9774 }
9775
9776 // Special case:
9777 // Allow "re-use" of a route's waypoints iff it is a simple
9778 // isolated route. This allows, for instance, creation of a closed
9779 // polygon route
9780 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9781
9782 if (!duplicate) {
9783 int dlg_return;
9784 dlg_return =
9785 OCPNMessageBox(this,
9786 _("Replace this RoutePoint by the nearby "
9787 "Waypoint?"),
9788 _("OpenCPN RoutePoint change"),
9789 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9790 if (dlg_return == wxID_YES) {
9791 /*double confirmation if the dragged point has been manually
9792 * created which can be important and could be deleted
9793 * unintentionally*/
9794 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9795 pNearbyPoint);
9796 current =
9797 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9798
9799 if (tail && current && (tail != current)) {
9800 int dlg_return1;
9801 connect = tail->GetIndexOf(pNearbyPoint);
9802 int index_current_route =
9803 current->GetIndexOf(m_pRoutePointEditTarget);
9804 index_last = current->GetIndexOf(current->GetLastPoint());
9805 dlg_return1 = wxID_NO;
9806 if (index_last ==
9807 index_current_route) { // we are dragging the last
9808 // point of the route
9809 if (connect != tail->GetnPoints()) { // anything to do?
9810
9811 wxString dmsg(
9812 _("Last part of route to be appended to dragged "
9813 "route?"));
9814 if (connect == 1)
9815 dmsg =
9816 _("Full route to be appended to dragged route?");
9817
9818 dlg_return1 = OCPNMessageBox(
9819 this, dmsg, _("OpenCPN Route Create"),
9820 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9821 if (dlg_return1 == wxID_YES) {
9822 appending = true;
9823 }
9824 }
9825 } else if (index_current_route ==
9826 1) { // dragging the first point of the route
9827 if (connect != 1) { // anything to do?
9828
9829 wxString dmsg(
9830 _("First part of route to be inserted into dragged "
9831 "route?"));
9832 if (connect == tail->GetnPoints())
9833 dmsg = _(
9834 "Full route to be inserted into dragged route?");
9835
9836 dlg_return1 = OCPNMessageBox(
9837 this, dmsg, _("OpenCPN Route Create"),
9838 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9839 if (dlg_return1 == wxID_YES) {
9840 inserting = true;
9841 }
9842 }
9843 }
9844 }
9845
9846 if (m_pRoutePointEditTarget->IsShared()) {
9847 dlg_return = wxID_NO;
9848 dlg_return = OCPNMessageBox(
9849 this,
9850 _("Do you really want to delete and replace this "
9851 "WayPoint") +
9852 "\n" + _("which has been created manually?"),
9853 ("OpenCPN RoutePoint warning"),
9854 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9855 }
9856 }
9857 if (dlg_return == wxID_YES) {
9858 pMousePoint = pNearbyPoint;
9859 if (pMousePoint->m_bIsolatedMark) {
9860 pMousePoint->SetShared(true);
9861 }
9862 pMousePoint->m_bIsolatedMark =
9863 false; // definitely no longer isolated
9864 pMousePoint->m_bIsInRoute = true;
9865 }
9866 }
9867 }
9868 }
9869 if (!pMousePoint)
9870 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9871
9872 if (m_pEditRouteArray) {
9873 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9874 ir++) {
9875 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9876 if (g_pRouteMan->IsRouteValid(pr)) {
9877 if (pMousePoint) { // replace dragged point by nearby one
9878 int nRP =
9879 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9880
9881 pSelect->DeleteAllSelectableRoutePoints(pr);
9882 pSelect->DeleteAllSelectableRouteSegments(pr);
9883
9884 pr->pRoutePointList->Insert(nRP, pMousePoint);
9885 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9886
9887 pSelect->AddAllSelectableRouteSegments(pr);
9888 pSelect->AddAllSelectableRoutePoints(pr);
9889 }
9890 pr->FinalizeForRendering();
9891 pr->UpdateSegmentDistances();
9892 pr->m_bIsBeingEdited = false;
9893
9894 if (m_bRoutePoinDragging) {
9895 // Special case optimization.
9896 // Dragging a single point of a route
9897 // without any point additions or re-ordering
9898 if (!pMousePoint)
9899 NavObj_dB::GetInstance().UpdateRoutePoint(
9900 m_pRoutePointEditTarget);
9901 else
9902 NavObj_dB::GetInstance().UpdateRoute(pr);
9903 }
9904 pr->SetHiLite(0);
9905 }
9906 }
9907 Refresh(false);
9908 }
9909
9910 if (appending) {
9911 // copy tail from connect until length to end of current after
9912 // dragging
9913
9914 int length = tail->GetnPoints();
9915 for (int i = connect + 1; i <= length; i++) {
9916 current->AddPointAndSegment(tail->GetPoint(i), false);
9917 if (current)
9918 current->m_lastMousePointIndex = current->GetnPoints();
9919 m_routeState++;
9920 gFrame->RefreshAllCanvas();
9921 ret = true;
9922 }
9923 current->FinalizeForRendering();
9924 current->m_bIsBeingEdited = false;
9925 FinishRoute();
9926 g_pRouteMan->DeleteRoute(tail);
9927 }
9928 if (inserting) {
9929 pSelect->DeleteAllSelectableRoutePoints(current);
9930 pSelect->DeleteAllSelectableRouteSegments(current);
9931 for (int i = 1; i < connect; i++) { // numbering in the tail route
9932 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9933 }
9934 pSelect->AddAllSelectableRouteSegments(current);
9935 pSelect->AddAllSelectableRoutePoints(current);
9936 current->FinalizeForRendering();
9937 current->m_bIsBeingEdited = false;
9938 g_pRouteMan->DeleteRoute(tail);
9939 }
9940
9941 // Update the RouteProperties Dialog, if currently shown
9942 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9943 if (m_pEditRouteArray) {
9944 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9945 ir++) {
9946 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9947 if (g_pRouteMan->IsRouteValid(pr)) {
9948 if (pRoutePropDialog->GetRoute() == pr) {
9949 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9950 }
9951 }
9952 }
9953 }
9954 }
9955
9956 if (pMousePoint) {
9957 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9958 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9959 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9960 // Hide mark properties dialog if open on the replaced point
9961 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9962 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9963 g_pMarkInfoDialog->Hide();
9964
9965 delete m_pRoutePointEditTarget;
9966 m_lastRoutePointEditTarget = NULL;
9967 undo->AfterUndoableAction(pMousePoint);
9968 undo->InvalidateUndo();
9969 } else {
9970 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9971 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9972
9973 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9974 }
9975
9976 delete m_pEditRouteArray;
9977 m_pEditRouteArray = NULL;
9978 }
9979
9980 InvalidateGL();
9981 m_bRouteEditing = false;
9982 m_pRoutePointEditTarget = NULL;
9983
9984 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9985 ret = true;
9986 }
9987
9988 else if (m_bMarkEditing) { // end of Waypoint drag
9989 if (m_pRoutePointEditTarget) {
9990 if (m_bRoutePoinDragging) {
9991 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9992 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9993 }
9994 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9995 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9996 if (!g_bopengl) {
9997 wxRect wp_rect;
9998 RoutePointGui(*m_pRoutePointEditTarget)
9999 .CalculateDCRect(m_dc_route, this, &wp_rect);
10000 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10001 RefreshRect(wp_rect, true);
10002 }
10003 }
10004 m_pRoutePointEditTarget = NULL;
10005 m_bMarkEditing = false;
10006 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10007 ret = true;
10008 }
10009
10010 else if (leftIsDown) { // left click for chart center
10011 leftIsDown = false;
10012 ret = false;
10013
10014 if (!g_btouch) {
10015 if (!m_bChartDragging && !m_bMeasure_Active) {
10016 } else {
10017 m_bChartDragging = false;
10018 }
10019 }
10020 }
10021 m_bRoutePoinDragging = false;
10022 } // !btouch
10023
10024 if (ret) return true;
10025 } // left up
10026
10027 if (event.RightDown()) {
10028 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10029 last_drag.x = mx;
10030 last_drag.y = my;
10031
10032 if (g_btouch) {
10033 // if( m_pRoutePointEditTarget )
10034 // return false;
10035 }
10036
10037 ret = true;
10038 m_FinishRouteOnKillFocus = false;
10039 CallPopupMenu(mx, my);
10040 m_FinishRouteOnKillFocus = true;
10041 } // Right down
10042
10043 return ret;
10044}
10045
10046bool panleftIsDown;
10047
10048bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10049 // Skip all mouse processing if shift is held.
10050 // This allows plugins to implement shift+drag behaviors.
10051 if (event.ShiftDown()) {
10052 return false;
10053 }
10054 int x, y;
10055 event.GetPosition(&x, &y);
10056
10057 x *= m_displayScale;
10058 y *= m_displayScale;
10059
10060 // Check for wheel rotation
10061 // ideally, should be just longer than the time between
10062 // processing accumulated mouse events from the event queue
10063 // as would happen during screen redraws.
10064 int wheel_dir = event.GetWheelRotation();
10065
10066 if (wheel_dir) {
10067 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10068 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10069
10070 double factor = g_mouse_zoom_sensitivity;
10071 if (wheel_dir < 0) factor = 1 / factor;
10072
10073 if (g_bsmoothpanzoom) {
10074 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10075 if (wheel_dir == m_last_wheel_dir) {
10076 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10077 // m_zoom_target /= factor;
10078 } else
10079 StopMovement();
10080 } else {
10081 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10082 m_wheelstopwatch.Start(0);
10083 // m_zoom_target = VPoint.chart_scale / factor;
10084 }
10085 }
10086
10087 m_last_wheel_dir = wheel_dir;
10088
10089 ZoomCanvas(factor, true, false);
10090 }
10091
10092 if (event.LeftDown()) {
10093 // Skip the first left click if it will cause a canvas focus shift
10094 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10095 // printf("focus shift\n");
10096 return false;
10097 }
10098
10099 last_drag.x = x, last_drag.y = y;
10100 panleftIsDown = true;
10101 }
10102
10103 if (event.LeftUp()) {
10104 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10105 // seen here.
10106 panleftIsDown = false;
10107
10108 if (!g_btouch) {
10109 if (!m_bChartDragging && !m_bMeasure_Active) {
10110 switch (cursor_region) {
10111 case MID_RIGHT: {
10112 PanCanvas(100, 0);
10113 break;
10114 }
10115
10116 case MID_LEFT: {
10117 PanCanvas(-100, 0);
10118 break;
10119 }
10120
10121 case MID_TOP: {
10122 PanCanvas(0, 100);
10123 break;
10124 }
10125
10126 case MID_BOT: {
10127 PanCanvas(0, -100);
10128 break;
10129 }
10130
10131 case CENTER: {
10132 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10133 break;
10134 }
10135 }
10136 } else {
10137 m_bChartDragging = false;
10138 }
10139 }
10140 }
10141 }
10142
10143 if (event.Dragging() && event.LeftIsDown()) {
10144 /*
10145 * fixed dragging.
10146 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10147 * before the drag event. Hence, as there is no mouse down event, last_drag
10148 * is not reset before the drag. And that results in one single drag
10149 * session, meaning you cannot drag the map a few miles north, lift your
10150 * finger, and the go even further north. Instead, the map resets itself
10151 * always to the very first drag start (since there is not reset of
10152 * last_drag).
10153 *
10154 * Besides, should not left down and dragging be enough of a situation to
10155 * start a drag procedure?
10156 *
10157 * Anyways, guarded it to be active in touch situations only.
10158 */
10159
10160 if (g_btouch) {
10161 struct timespec now;
10162 clock_gettime(CLOCK_MONOTONIC, &now);
10163 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10164
10165 if (false == m_bChartDragging) {
10166 // Reset drag calculation members
10167 last_drag.x = x, last_drag.y = y;
10168 m_bChartDragging = true;
10169 m_chart_drag_total_time = 0;
10170 m_chart_drag_total_x = 0;
10171 m_chart_drag_total_y = 0;
10172 m_inertia_last_drag_x = x;
10173 m_inertia_last_drag_y = y;
10174 m_drag_vec_x.clear();
10175 m_drag_vec_y.clear();
10176 m_drag_vec_t.clear();
10177 m_last_drag_time = tnow;
10178 }
10179
10180 // Calculate and store drag dynamics.
10181 uint64_t delta_t = tnow - m_last_drag_time;
10182 double delta_tf = delta_t / 1e9;
10183
10184 m_chart_drag_total_time += delta_tf;
10185 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10186 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10187
10188 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10189 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10190 m_drag_vec_t.push_back(delta_tf);
10191
10192 m_inertia_last_drag_x = x;
10193 m_inertia_last_drag_y = y;
10194 m_last_drag_time = tnow;
10195
10196 if ((last_drag.x != x) || (last_drag.y != y)) {
10197 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10198 // dragging on route create.
10199 // github #2994
10200 m_bChartDragging = true;
10201 StartTimedMovement();
10202 m_pan_drag.x += last_drag.x - x;
10203 m_pan_drag.y += last_drag.y - y;
10204 last_drag.x = x, last_drag.y = y;
10205 }
10206 }
10207 } else {
10208 if ((last_drag.x != x) || (last_drag.y != y)) {
10209 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10210 // dragging on route create.
10211 // github #2994
10212 m_bChartDragging = true;
10213 StartTimedMovement();
10214 m_pan_drag.x += last_drag.x - x;
10215 m_pan_drag.y += last_drag.y - y;
10216 last_drag.x = x, last_drag.y = y;
10217 }
10218 }
10219 }
10220
10221 // Handle some special cases
10222 if (g_btouch) {
10223 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10224 // deactivate next LeftUp to ovoid creating an unexpected point
10225 m_DoubleClickTimer->Start();
10226 singleClickEventIsValid = false;
10227 }
10228 }
10229 }
10230
10231 return true;
10232}
10233
10234void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10235 if (MouseEventOverlayWindows(event)) return;
10236
10237 if (MouseEventSetup(event)) return; // handled, no further action required
10238
10239 if (!MouseEventProcessObjects(event)) MouseEventProcessCanvas(event);
10240}
10241
10242void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10243 // Switch to the appropriate cursor on mouse movement
10244
10245 wxCursor *ptarget_cursor = pCursorArrow;
10246 if (!pPlugIn_Cursor) {
10247 ptarget_cursor = pCursorArrow;
10248 if ((!m_routeState) &&
10249 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10250 if (cursor_region == MID_RIGHT) {
10251 ptarget_cursor = pCursorRight;
10252 } else if (cursor_region == MID_LEFT) {
10253 ptarget_cursor = pCursorLeft;
10254 } else if (cursor_region == MID_TOP) {
10255 ptarget_cursor = pCursorDown;
10256 } else if (cursor_region == MID_BOT) {
10257 ptarget_cursor = pCursorUp;
10258 } else {
10259 ptarget_cursor = pCursorArrow;
10260 }
10261 } else if (m_bMeasure_Active ||
10262 m_routeState) // If Measure tool use Pencil Cursor
10263 ptarget_cursor = pCursorPencil;
10264 } else {
10265 ptarget_cursor = pPlugIn_Cursor;
10266 }
10267
10268 SetCursor(*ptarget_cursor);
10269}
10270
10271void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10272 SetCursor(*pCursorArrow);
10273}
10274
10275void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10276 ChartPlugInWrapper *target_plugin_chart = NULL;
10277 s57chart *Chs57 = NULL;
10278 wxFileName file;
10279 wxArrayString files;
10280
10281 ChartBase *target_chart = GetChartAtCursor();
10282 if (target_chart) {
10283 file.Assign(target_chart->GetFullPath());
10284 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10285 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10286 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10287 else
10288 Chs57 = dynamic_cast<s57chart *>(target_chart);
10289 } else { // target_chart = null, might be mbtiles
10290 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10291 unsigned int im = stackIndexArray.size();
10292 int scale = 2147483647; // max 32b integer
10293 if (VPoint.b_quilt && im > 0) {
10294 for (unsigned int is = 0; is < im; is++) {
10295 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10296 CHART_TYPE_MBTILES) {
10297 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10298 double lat, lon;
10299 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10300 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10301 .GetBBox()
10302 .Contains(lat, lon)) {
10303 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10304 scale) {
10305 scale =
10306 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10307 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10308 }
10309 }
10310 }
10311 }
10312 }
10313 }
10314
10315 std::vector<Ais8_001_22 *> area_notices;
10316
10317 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10318 float vp_scale = GetVPScale();
10319
10320 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10321 auto target_data = target.second;
10322 if (!target_data->area_notices.empty()) {
10323 for (auto &ani : target_data->area_notices) {
10324 Ais8_001_22 &area_notice = ani.second;
10325
10326 BoundingBox bbox;
10327
10328 for (Ais8_001_22_SubAreaList::iterator sa =
10329 area_notice.sub_areas.begin();
10330 sa != area_notice.sub_areas.end(); ++sa) {
10331 switch (sa->shape) {
10332 case AIS8_001_22_SHAPE_CIRCLE: {
10333 wxPoint target_point;
10334 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10335 bbox.Expand(target_point);
10336 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10337 break;
10338 }
10339 case AIS8_001_22_SHAPE_RECT: {
10340 wxPoint target_point;
10341 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10342 bbox.Expand(target_point);
10343 if (sa->e_dim_m > sa->n_dim_m)
10344 bbox.EnLarge(sa->e_dim_m * vp_scale);
10345 else
10346 bbox.EnLarge(sa->n_dim_m * vp_scale);
10347 break;
10348 }
10349 case AIS8_001_22_SHAPE_POLYGON:
10350 case AIS8_001_22_SHAPE_POLYLINE: {
10351 for (int i = 0; i < 4; ++i) {
10352 double lat = sa->latitude;
10353 double lon = sa->longitude;
10354 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10355 &lat, &lon);
10356 wxPoint target_point;
10357 GetCanvasPointPix(lat, lon, &target_point);
10358 bbox.Expand(target_point);
10359 }
10360 break;
10361 }
10362 case AIS8_001_22_SHAPE_SECTOR: {
10363 double lat1 = sa->latitude;
10364 double lon1 = sa->longitude;
10365 double lat, lon;
10366 wxPoint target_point;
10367 GetCanvasPointPix(lat1, lon1, &target_point);
10368 bbox.Expand(target_point);
10369 for (int i = 0; i < 18; ++i) {
10370 ll_gc_ll(
10371 lat1, lon1,
10372 sa->left_bound_deg +
10373 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10374 sa->radius_m / 1852.0, &lat, &lon);
10375 GetCanvasPointPix(lat, lon, &target_point);
10376 bbox.Expand(target_point);
10377 }
10378 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10379 &lat, &lon);
10380 GetCanvasPointPix(lat, lon, &target_point);
10381 bbox.Expand(target_point);
10382 break;
10383 }
10384 }
10385 }
10386
10387 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10388 area_notices.push_back(&area_notice);
10389 }
10390 }
10391 }
10392 }
10393 }
10394
10395 if (target_chart || !area_notices.empty() || file.HasName()) {
10396 // Go get the array of all objects at the cursor lat/lon
10397 int sel_rad_pix = 5;
10398 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10399
10400 // Make sure we always get the lights from an object, even if we are
10401 // currently not displaying lights on the chart.
10402
10403 SetCursor(wxCURSOR_WAIT);
10404 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10405 if (!lightsVis) SetShowENCLights(true);
10406 ;
10407
10408 ListOfObjRazRules *rule_list = NULL;
10409 ListOfPI_S57Obj *pi_rule_list = NULL;
10410 if (Chs57)
10411 rule_list =
10412 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10413 else if (target_plugin_chart)
10414 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10415 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10416
10417 ListOfObjRazRules *overlay_rule_list = NULL;
10418 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10419 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10420
10421 if (CHs57_Overlay) {
10422 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10423 zlat, zlon, SelectRadius, &GetVP());
10424 }
10425
10426 if (!lightsVis) SetShowENCLights(false);
10427
10428 wxString objText;
10429 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10430 wxString face = dFont->GetFaceName();
10431
10432 if (NULL == g_pObjectQueryDialog) {
10433 g_pObjectQueryDialog =
10434 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10435 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10436 }
10437
10438 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10439 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10440
10441#ifdef __WXOSX__
10442 // Auto Adjustment for dark mode
10443 fg = g_pObjectQueryDialog->GetForegroundColour();
10444#endif
10445
10446 objText.Printf(
10447 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10448 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10449
10450#ifdef __WXOSX__
10451 int points = dFont->GetPointSize();
10452#else
10453 int points = dFont->GetPointSize() + 1;
10454#endif
10455
10456 int sizes[7];
10457 for (int i = -2; i < 5; i++) {
10458 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10459 }
10460 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10461
10462 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10463
10464 if (overlay_rule_list && CHs57_Overlay) {
10465 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10466 objText << "<hr noshade>";
10467 }
10468
10469 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10470 an != area_notices.end(); ++an) {
10471 objText << "<b>AIS Area Notice:</b> ";
10472 objText << ais8_001_22_notice_names[(*an)->notice_type];
10473 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10474 (*an)->sub_areas.begin();
10475 sa != (*an)->sub_areas.end(); ++sa)
10476 if (!sa->text.empty()) objText << sa->text;
10477 objText << "<br>expires: " << (*an)->expiry_time.Format();
10478 objText << "<hr noshade>";
10479 }
10480
10481 if (Chs57)
10482 objText << Chs57->CreateObjDescriptions(rule_list);
10483 else if (target_plugin_chart)
10484 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10485 pi_rule_list);
10486
10487 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10488
10489 // Add the additional info files
10490 wxString AddFiles, filenameOK;
10491 int filecount = 0;
10492 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10493 // plugin
10494
10495 AddFiles = wxString::Format(
10496 "<hr noshade><br><b>Additional info files attached to: </b> "
10497 "<font "
10498 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10499 "cellpadding=3>",
10500 file.GetFullName());
10501 file.Normalize();
10502 file.Assign(file.GetPath(), "");
10503 wxDir dir(file.GetFullPath());
10504 wxString filename;
10505 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10506 while (cont) {
10507 file.Assign(dir.GetNameWithSep().append(filename));
10508 wxString FormatString =
10509 "<td valign=top><font size=-2><a "
10510 "href=\"%s\">%s</a></font></td>";
10511 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10512 filenameOK = file.GetFullPath(); // remember last valid name
10513 // we are making a 3 columns table. New row only every third file
10514 if (3 * ((int)filecount / 3) == filecount)
10515 FormatString.Prepend("<tr>"); // new row
10516 else
10517 FormatString.Prepend(
10518 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10519 // spacer column
10520
10521 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10522 file.GetFullName());
10523 filecount++;
10524 }
10525 cont = dir.GetNext(&filename);
10526 }
10527 objText << AddFiles << "</table>";
10528 }
10529 objText << "</font>";
10530 objText << "</body></html>";
10531
10532 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10533 g_pObjectQueryDialog->SetHTMLPage(objText);
10534 g_pObjectQueryDialog->Show();
10535 }
10536 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10537 // generate an event to avoid double code
10538 wxHtmlLinkInfo hli(filenameOK);
10539 wxHtmlLinkEvent hle(1, hli);
10540 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10541 }
10542
10543 if (rule_list) rule_list->Clear();
10544 delete rule_list;
10545
10546 if (overlay_rule_list) overlay_rule_list->Clear();
10547 delete overlay_rule_list;
10548
10549 if (pi_rule_list) pi_rule_list->Clear();
10550 delete pi_rule_list;
10551
10552 SetCursor(wxCURSOR_ARROW);
10553 }
10554}
10555
10556void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10557 bool bNew = false;
10558 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10559 // Dialog
10560 g_pMarkInfoDialog = new MarkInfoDlg(this);
10561 bNew = true;
10562 }
10563
10564 if (1 /*g_bresponsive*/) {
10565 wxSize canvas_size = GetSize();
10566
10567 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10568 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10569
10570 g_pMarkInfoDialog->Layout();
10571
10572 wxPoint canvas_pos = GetPosition();
10573 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10574
10575 bool newFit = false;
10576 if (canvas_size.x < fitted_size.x) {
10577 fitted_size.x = canvas_size.x - 40;
10578 if (canvas_size.y < fitted_size.y)
10579 fitted_size.y -= 40; // scrollbar added
10580 }
10581 if (canvas_size.y < fitted_size.y) {
10582 fitted_size.y = canvas_size.y - 40;
10583 if (canvas_size.x < fitted_size.x)
10584 fitted_size.x -= 40; // scrollbar added
10585 }
10586
10587 if (newFit) {
10588 g_pMarkInfoDialog->SetSize(fitted_size);
10589 g_pMarkInfoDialog->Centre();
10590 }
10591 }
10592
10593 markPoint->m_bRPIsBeingEdited = false;
10594
10595 wxString title_base = _("Mark Properties");
10596 if (markPoint->m_bIsInRoute) {
10597 title_base = _("Waypoint Properties");
10598 }
10599 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10600 g_pMarkInfoDialog->UpdateProperties();
10601 if (markPoint->m_bIsInLayer) {
10602 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10603 GetLayerName(markPoint->m_LayerID)));
10604 g_pMarkInfoDialog->SetDialogTitle(caption);
10605 } else
10606 g_pMarkInfoDialog->SetDialogTitle(title_base);
10607
10608 g_pMarkInfoDialog->Show();
10609 g_pMarkInfoDialog->Raise();
10610 g_pMarkInfoDialog->InitialFocus();
10611 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10612}
10613
10614void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10615 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10616 pRoutePropDialog->SetRouteAndUpdate(selected);
10617 // pNew->UpdateProperties();
10618 pRoutePropDialog->Show();
10619 pRoutePropDialog->Raise();
10620 return;
10621 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10622 this); // There is one global instance of the RouteProp Dialog
10623
10624 if (g_bresponsive) {
10625 wxSize canvas_size = GetSize();
10626 wxPoint canvas_pos = GetPosition();
10627 wxSize fitted_size = pRoutePropDialog->GetSize();
10628 ;
10629
10630 if (canvas_size.x < fitted_size.x) {
10631 fitted_size.x = canvas_size.x;
10632 if (canvas_size.y < fitted_size.y)
10633 fitted_size.y -= 20; // scrollbar added
10634 }
10635 if (canvas_size.y < fitted_size.y) {
10636 fitted_size.y = canvas_size.y;
10637 if (canvas_size.x < fitted_size.x)
10638 fitted_size.x -= 20; // scrollbar added
10639 }
10640
10641 pRoutePropDialog->SetSize(fitted_size);
10642 pRoutePropDialog->Centre();
10643
10644 // int xp = (canvas_size.x - fitted_size.x)/2;
10645 // int yp = (canvas_size.y - fitted_size.y)/2;
10646
10647 wxPoint xxp = ClientToScreen(canvas_pos);
10648 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10649 }
10650
10651 pRoutePropDialog->SetRouteAndUpdate(selected);
10652
10653 pRoutePropDialog->Show();
10654
10655 Refresh(false);
10656}
10657
10658void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10659 pTrackPropDialog = TrackPropDlg::getInstance(
10660 this); // There is one global instance of the RouteProp Dialog
10661
10662 pTrackPropDialog->SetTrackAndUpdate(selected);
10663 pTrackPropDialog->UpdateProperties();
10664
10665 pTrackPropDialog->Show();
10666
10667 Refresh(false);
10668}
10669
10670void pupHandler_PasteWaypoint() {
10671 Kml kml;
10672
10673 int pasteBuffer = kml.ParsePasteBuffer();
10674 RoutePoint *pasted = kml.GetParsedRoutePoint();
10675 if (!pasted) return;
10676
10677 double nearby_radius_meters =
10678 g_Platform->GetSelectRadiusPix() /
10679 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10680
10681 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10682 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10683
10684 int answer = wxID_NO;
10685 if (nearPoint && !nearPoint->m_bIsInLayer) {
10686 wxString msg;
10687 msg << _(
10688 "There is an existing waypoint at the same location as the one you are "
10689 "pasting. Would you like to merge the pasted data with it?\n\n");
10690 msg << _("Answering 'No' will create a new waypoint at the same location.");
10691 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10692 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10693 }
10694
10695 if (answer == wxID_YES) {
10696 nearPoint->SetName(pasted->GetName());
10697 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10698 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10699 pRouteManagerDialog->UpdateWptListCtrl();
10700 }
10701
10702 if (answer == wxID_NO) {
10703 RoutePoint *newPoint = new RoutePoint(pasted);
10704 newPoint->m_bIsolatedMark = true;
10705 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10706 newPoint);
10707 // pConfig->AddNewWayPoint(newPoint, -1);
10708 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10709
10710 pWayPointMan->AddRoutePoint(newPoint);
10711 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10712 pRouteManagerDialog->UpdateWptListCtrl();
10713 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10714 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10715 }
10716
10717 gFrame->InvalidateAllGL();
10718 gFrame->RefreshAllCanvas(false);
10719}
10720
10721void pupHandler_PasteRoute() {
10722 Kml kml;
10723
10724 int pasteBuffer = kml.ParsePasteBuffer();
10725 Route *pasted = kml.GetParsedRoute();
10726 if (!pasted) return;
10727
10728 double nearby_radius_meters =
10729 g_Platform->GetSelectRadiusPix() /
10730 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10731
10732 RoutePoint *curPoint;
10733 RoutePoint *nearPoint;
10734 RoutePoint *prevPoint = NULL;
10735
10736 bool mergepoints = false;
10737 bool createNewRoute = true;
10738 int existingWaypointCounter = 0;
10739
10740 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10741 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10742 nearPoint = pWayPointMan->GetNearbyWaypoint(
10743 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10744 if (nearPoint) {
10745 mergepoints = true;
10746 existingWaypointCounter++;
10747 // Small hack here to avoid both extending RoutePoint and repeating all
10748 // the GetNearbyWaypoint calculations. Use existin data field in
10749 // RoutePoint as temporary storage.
10750 curPoint->m_bPtIsSelected = true;
10751 }
10752 }
10753
10754 int answer = wxID_NO;
10755 if (mergepoints) {
10756 wxString msg;
10757 msg << _(
10758 "There are existing waypoints at the same location as some of the ones "
10759 "you are pasting. Would you like to just merge the pasted data into "
10760 "them?\n\n");
10761 msg << _("Answering 'No' will create all new waypoints for this route.");
10762 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10763 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10764
10765 if (answer == wxID_CANCEL) {
10766 return;
10767 }
10768 }
10769
10770 // If all waypoints exist since before, and a route with the same name, we
10771 // don't create a new route.
10772 if (mergepoints && answer == wxID_YES &&
10773 existingWaypointCounter == pasted->GetnPoints()) {
10774 wxRouteListNode *route_node = pRouteList->GetFirst();
10775 while (route_node) {
10776 Route *proute = route_node->GetData();
10777
10778 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10779 createNewRoute = false;
10780 break;
10781 }
10782 route_node = route_node->GetNext();
10783 }
10784 }
10785
10786 Route *newRoute = 0;
10787 RoutePoint *newPoint = 0;
10788
10789 if (createNewRoute) {
10790 newRoute = new Route();
10791 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10792 }
10793
10794 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10795 curPoint = pasted->GetPoint(i);
10796 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10797 curPoint->m_bPtIsSelected = false;
10798 newPoint = pWayPointMan->GetNearbyWaypoint(
10799 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10800 newPoint->SetName(curPoint->GetName());
10801 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10802
10803 if (createNewRoute) newRoute->AddPoint(newPoint);
10804 } else {
10805 curPoint->m_bPtIsSelected = false;
10806
10807 newPoint = new RoutePoint(curPoint);
10808 newPoint->m_bIsolatedMark = false;
10809 newPoint->SetIconName("circle");
10810 newPoint->m_bIsVisible = true;
10811 newPoint->m_bShowName = false;
10812 newPoint->SetShared(false);
10813
10814 newRoute->AddPoint(newPoint);
10815 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10816 newPoint);
10817 // pConfig->AddNewWayPoint(newPoint, -1);
10818 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10819 pWayPointMan->AddRoutePoint(newPoint);
10820 }
10821 if (i > 1 && createNewRoute)
10822 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10823 curPoint->m_lat, curPoint->m_lon,
10824 prevPoint, newPoint, newRoute);
10825 prevPoint = newPoint;
10826 }
10827
10828 if (createNewRoute) {
10829 pRouteList->Append(newRoute);
10830 // pConfig->AddNewRoute(newRoute); // use auto next num
10831 NavObj_dB::GetInstance().InsertRoute(newRoute);
10832
10833 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10834 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10835 }
10836
10837 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10838 pRouteManagerDialog->UpdateRouteListCtrl();
10839 pRouteManagerDialog->UpdateWptListCtrl();
10840 }
10841 gFrame->InvalidateAllGL();
10842 gFrame->RefreshAllCanvas(false);
10843 }
10844 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10845 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10846}
10847
10848void pupHandler_PasteTrack() {
10849 Kml kml;
10850
10851 int pasteBuffer = kml.ParsePasteBuffer();
10852 Track *pasted = kml.GetParsedTrack();
10853 if (!pasted) return;
10854
10855 TrackPoint *curPoint;
10856
10857 Track *newTrack = new Track();
10858 TrackPoint *newPoint;
10859 TrackPoint *prevPoint = NULL;
10860
10861 newTrack->SetName(pasted->GetName());
10862
10863 for (int i = 0; i < pasted->GetnPoints(); i++) {
10864 curPoint = pasted->GetPoint(i);
10865
10866 newPoint = new TrackPoint(curPoint);
10867
10868 wxDateTime now = wxDateTime::Now();
10869 newPoint->SetCreateTime(curPoint->GetCreateTime());
10870
10871 newTrack->AddPoint(newPoint);
10872
10873 if (prevPoint)
10874 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10875 newPoint->m_lat, newPoint->m_lon,
10876 prevPoint, newPoint, newTrack);
10877
10878 prevPoint = newPoint;
10879 }
10880
10881 g_TrackList.push_back(newTrack);
10882 // pConfig->AddNewTrack(newTrack);
10883 NavObj_dB::GetInstance().InsertTrack(newTrack);
10884
10885 gFrame->InvalidateAllGL();
10886 gFrame->RefreshAllCanvas(false);
10887}
10888
10889bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
10890 wxJSONValue v;
10891 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
10892 v["CursorPosition_x"] = x;
10893 v["CursorPosition_y"] = y;
10894 // Send a limited set of selection types depending on what is
10895 // found under the mouse point.
10896 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
10897 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
10898 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
10899
10900 wxJSONWriter w;
10901 wxString out;
10902 w.Write(v, out);
10903 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
10904
10905 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
10906
10907#if 0
10908#define SELTYPE_UNKNOWN 0x0001
10909#define SELTYPE_ROUTEPOINT 0x0002
10910#define SELTYPE_ROUTESEGMENT 0x0004
10911#define SELTYPE_TIDEPOINT 0x0008
10912#define SELTYPE_CURRENTPOINT 0x0010
10913#define SELTYPE_ROUTECREATE 0x0020
10914#define SELTYPE_AISTARGET 0x0040
10915#define SELTYPE_MARKPOINT 0x0080
10916#define SELTYPE_TRACKSEGMENT 0x0100
10917#define SELTYPE_DRAGHANDLE 0x0200
10918#endif
10919
10920 if (g_bhide_context_menus) return true;
10921 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
10922 m_pFoundRoutePoint, m_FoundAIS_MMSI,
10923 m_pIDXCandidate, m_nmea_log);
10924
10925 Connect(
10926 wxEVT_COMMAND_MENU_SELECTED,
10927 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10928
10929 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
10930
10931 Disconnect(
10932 wxEVT_COMMAND_MENU_SELECTED,
10933 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10934
10935 delete m_canvasMenu;
10936 m_canvasMenu = NULL;
10937
10938#ifdef __WXQT__
10939 // gFrame->SurfaceToolbar();
10940 // g_MainToolbar->Raise();
10941#endif
10942
10943 return true;
10944}
10945
10946void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
10947 // Pass menu events from the canvas to the menu handler
10948 // This is necessarily in ChartCanvas since that is the menu's parent.
10949 if (m_canvasMenu) {
10950 m_canvasMenu->PopupMenuHandler(event);
10951 }
10952 return;
10953}
10954
10955void ChartCanvas::StartRoute(void) {
10956 // Do not allow more than one canvas to create a route at one time.
10957 if (g_brouteCreating) return;
10958
10959 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
10960
10961 g_brouteCreating = true;
10962 m_routeState = 1;
10963 m_bDrawingRoute = false;
10964 SetCursor(*pCursorPencil);
10965 // SetCanvasToolbarItemState(ID_ROUTE, true);
10966 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
10967
10968 HideGlobalToolbar();
10969
10970#ifdef __ANDROID__
10971 androidSetRouteAnnunciator(true);
10972#endif
10973}
10974
10975wxString ChartCanvas::FinishRoute(void) {
10976 m_routeState = 0;
10977 m_prev_pMousePoint = NULL;
10978 m_bDrawingRoute = false;
10979 wxString rv = "";
10980 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
10981
10982 // SetCanvasToolbarItemState(ID_ROUTE, false);
10983 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
10984#ifdef __ANDROID__
10985 androidSetRouteAnnunciator(false);
10986#endif
10987
10988 SetCursor(*pCursorArrow);
10989
10990 if (m_pMouseRoute) {
10991 if (m_bAppendingRoute) {
10992 // pConfig->UpdateRoute(m_pMouseRoute);
10993 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
10994 } else {
10995 if (m_pMouseRoute->GetnPoints() > 1) {
10996 // pConfig->AddNewRoute(m_pMouseRoute);
10997 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
10998 } else {
10999 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11000 m_pMouseRoute = NULL;
11001 }
11002 }
11003 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11004
11005 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11006 (pRoutePropDialog->IsShown())) {
11007 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11008 }
11009
11010 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11011 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11012 pRouteManagerDialog->UpdateRouteListCtrl();
11013 }
11014 }
11015 m_bAppendingRoute = false;
11016 m_pMouseRoute = NULL;
11017
11018 m_pSelectedRoute = NULL;
11019
11020 undo->InvalidateUndo();
11021 gFrame->RefreshAllCanvas(true);
11022
11023 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11024
11025 ShowGlobalToolbar();
11026
11027 g_brouteCreating = false;
11028
11029 return rv;
11030}
11031
11032void ChartCanvas::HideGlobalToolbar() {
11033 if (m_canvasIndex == 0) {
11034 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
11035 }
11036}
11037
11038void ChartCanvas::ShowGlobalToolbar() {
11039 if (m_canvasIndex == 0) {
11040 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
11041 }
11042}
11043
11044void ChartCanvas::ShowAISTargetList(void) {
11045 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11046 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11047 }
11048
11049 g_pAISTargetList->UpdateAISTargetList();
11050}
11051
11052void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11053 if (!m_bShowOutlines) return;
11054
11055 if (!ChartData) return;
11056
11057 int nEntry = ChartData->GetChartTableEntries();
11058
11059 for (int i = 0; i < nEntry; i++) {
11060 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11061
11062 // Check to see if the candidate chart is in the currently active group
11063 bool b_group_draw = false;
11064 if (m_groupIndex > 0) {
11065 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11066 int index = pt->GetGroupArray()[ig];
11067 if (m_groupIndex == index) {
11068 b_group_draw = true;
11069 break;
11070 }
11071 }
11072 } else
11073 b_group_draw = true;
11074
11075 if (b_group_draw) RenderChartOutline(dc, i, vp);
11076 }
11077
11078 // On CM93 Composite Charts, draw the outlines of the next smaller
11079 // scale cell
11080 cm93compchart *pcm93 = NULL;
11081 if (VPoint.b_quilt) {
11082 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11083 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11084 pcm93 = (cm93compchart *)pch;
11085 break;
11086 }
11087 } else if (m_singleChart &&
11088 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11089 pcm93 = (cm93compchart *)m_singleChart;
11090
11091 if (pcm93) {
11092 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11093 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11094
11095 if (zoom_factor > 8.0) {
11096 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11097 dc.SetPen(mPen);
11098 } else {
11099 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11100 dc.SetPen(mPen);
11101 }
11102
11103 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11104 }
11105}
11106
11107void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11108#ifdef ocpnUSE_GL
11109 if (g_bopengl && m_glcc) {
11110 /* opengl version specially optimized */
11111 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11112 return;
11113 }
11114#endif
11115
11116 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11117 if (!ChartData->IsChartAvailable(dbIndex)) return;
11118 }
11119
11120 float plylat, plylon;
11121 float plylat1, plylon1;
11122
11123 int pixx, pixy, pixx1, pixy1;
11124
11125 LLBBox box;
11126 ChartData->GetDBBoundingBox(dbIndex, box);
11127
11128 // Don't draw an outline in the case where the chart covers the entire world
11129 // */
11130 if (box.GetLonRange() == 360) return;
11131
11132 double lon_bias = 0;
11133 // chart is outside of viewport lat/lon bounding box
11134 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11135
11136 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11137
11138 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11139 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11140
11141 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11142 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11143
11144 else
11145 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11146
11147 // Are there any aux ply entries?
11148 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11149 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11150 {
11151 wxPoint r, r1;
11152
11153 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11154 plylon += lon_bias;
11155
11156 GetCanvasPointPix(plylat, plylon, &r);
11157 pixx = r.x;
11158 pixy = r.y;
11159
11160 for (int i = 0; i < nPly - 1; i++) {
11161 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11162 plylon1 += lon_bias;
11163
11164 GetCanvasPointPix(plylat1, plylon1, &r1);
11165 pixx1 = r1.x;
11166 pixy1 = r1.y;
11167
11168 int pixxs1 = pixx1;
11169 int pixys1 = pixy1;
11170
11171 bool b_skip = false;
11172
11173 if (vp.chart_scale > 5e7) {
11174 // calculate projected distance between these two points in meters
11175 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11176 pow((double)(pixy1 - pixy), 2)) /
11177 vp.view_scale_ppm;
11178
11179 if (dist > 0.0) {
11180 // calculate GC distance between these two points in meters
11181 double distgc =
11182 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11183
11184 // If the distances are nonsense, it means that the scale is very
11185 // small and the segment wrapped the world So skip it....
11186 // TODO improve this to draw two segments
11187 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11188 b_skip = true;
11189 } else
11190 b_skip = true;
11191 }
11192
11193 ClipResult res = cohen_sutherland_line_clip_i(
11194 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11195 if (res != Invisible && !b_skip)
11196 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11197
11198 plylat = plylat1;
11199 plylon = plylon1;
11200 pixx = pixxs1;
11201 pixy = pixys1;
11202 }
11203
11204 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11205 plylon1 += lon_bias;
11206
11207 GetCanvasPointPix(plylat1, plylon1, &r1);
11208 pixx1 = r1.x;
11209 pixy1 = r1.y;
11210
11211 ClipResult res = cohen_sutherland_line_clip_i(
11212 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11213 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11214 }
11215
11216 else // Use Aux PlyPoints
11217 {
11218 wxPoint r, r1;
11219
11220 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11221 for (int j = 0; j < nAuxPlyEntries; j++) {
11222 int nAuxPly =
11223 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11224 GetCanvasPointPix(plylat, plylon, &r);
11225 pixx = r.x;
11226 pixy = r.y;
11227
11228 for (int i = 0; i < nAuxPly - 1; i++) {
11229 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11230
11231 GetCanvasPointPix(plylat1, plylon1, &r1);
11232 pixx1 = r1.x;
11233 pixy1 = r1.y;
11234
11235 int pixxs1 = pixx1;
11236 int pixys1 = pixy1;
11237
11238 bool b_skip = false;
11239
11240 if (vp.chart_scale > 5e7) {
11241 // calculate projected distance between these two points in meters
11242 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11243 ((pixy1 - pixy) * (pixy1 - pixy))) /
11244 vp.view_scale_ppm;
11245 if (dist > 0.0) {
11246 // calculate GC distance between these two points in meters
11247 double distgc =
11248 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11249
11250 // If the distances are nonsense, it means that the scale is very
11251 // small and the segment wrapped the world So skip it....
11252 // TODO improve this to draw two segments
11253 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11254 b_skip = true;
11255 } else
11256 b_skip = true;
11257 }
11258
11259 ClipResult res = cohen_sutherland_line_clip_i(
11260 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11261 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11262
11263 plylat = plylat1;
11264 plylon = plylon1;
11265 pixx = pixxs1;
11266 pixy = pixys1;
11267 }
11268
11269 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11270 GetCanvasPointPix(plylat1, plylon1, &r1);
11271 pixx1 = r1.x;
11272 pixy1 = r1.y;
11273
11274 ClipResult res = cohen_sutherland_line_clip_i(
11275 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11276 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11277 }
11278 }
11279}
11280
11281static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
11282 const wxString &second) {
11283 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11284
11285 int pointsize = dFont->GetPointSize();
11286 pointsize /= OCPN_GetWinDIPScaleFactor();
11287
11288 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11289 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11290 false, dFont->GetFaceName());
11291
11292 dc.SetFont(*psRLI_font);
11293
11294 int w1, h1;
11295 int w2 = 0;
11296 int h2 = 0;
11297 int h, w;
11298
11299 int xp, yp;
11300 int hilite_offset = 3;
11301#ifdef __WXMAC__
11302 wxScreenDC sdc;
11303 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
11304 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
11305#else
11306 dc.GetTextExtent(first, &w1, &h1);
11307 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
11308#endif
11309
11310 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11311 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11312
11313 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
11314 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11315
11316 h = h1 + h2;
11317
11318 xp = ref_point.x - w;
11319 yp = ref_point.y;
11320 yp += hilite_offset;
11321
11322 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11323
11324 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11325 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11326
11327 dc.DrawText(first, xp, yp);
11328 if (second.Len()) dc.DrawText(second, xp, yp + h1);
11329}
11330
11331void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11332 if (!g_bAllowShipToActive) return;
11333
11334 Route *rt = g_pRouteMan->GetpActiveRoute();
11335 if (!rt) return;
11336
11337 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11338 wxPoint2DDouble pa, pb;
11339 GetDoubleCanvasPointPix(gLat, gLon, &pa);
11340 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11341
11342 // set pen
11343 int width =
11344 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11345 if (rt->m_width != wxPENSTYLE_INVALID)
11346 width = rt->m_width; // set route pen style if any
11347 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11348 g_shipToActiveStyle, 5)]; // get setting pen style
11349 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11350 wxColour color =
11351 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11352 : // set setting route pen color
11353 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11354 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11355
11356 dc.SetPen(*mypen);
11357 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11358
11359 if (!Use_Opengl)
11360 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11361 (int)pb.m_y, GetVP(), true);
11362
11363#ifdef ocpnUSE_GL
11364 else {
11365#ifdef USE_ANDROID_GLES2
11366 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11367#else
11368 if (style != wxPENSTYLE_SOLID) {
11369 if (glChartCanvas::dash_map.find(style) !=
11370 glChartCanvas::dash_map.end()) {
11371 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11372 dc.SetPen(*mypen);
11373 }
11374 }
11375 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11376#endif
11377
11378 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11379 (int)pb.m_x, (int)pb.m_y, GetVP());
11380 }
11381#endif
11382 }
11383}
11384
11385void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11386 Route *route = 0;
11387 if (m_routeState >= 2) route = m_pMouseRoute;
11388 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11389 route = m_pMeasureRoute;
11390
11391 if (!route) return;
11392
11393 // Validate route pointer
11394 if (!g_pRouteMan->IsRouteValid(route)) return;
11395
11396 double render_lat = m_cursor_lat;
11397 double render_lon = m_cursor_lon;
11398
11399 int np = route->GetnPoints();
11400 if (np) {
11401 if (g_btouch && (np > 1)) np--;
11402 RoutePoint rp = route->GetPoint(np);
11403 render_lat = rp.m_lat;
11404 render_lon = rp.m_lon;
11405 }
11406
11407 double rhumbBearing, rhumbDist;
11408 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11409 &rhumbBearing, &rhumbDist);
11410 double brg = rhumbBearing;
11411 double dist = rhumbDist;
11412
11413 // Skip GreatCircle rubberbanding on touch devices.
11414 if (!g_btouch) {
11415 double gcBearing, gcBearing2, gcDist;
11416 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11417 m_cursor_lat, &gcDist, &gcBearing,
11418 &gcBearing2);
11419 double gcDistm = gcDist / 1852.0;
11420
11421 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11422 rhumbBearing = 90.;
11423
11424 wxPoint destPoint, lastPoint;
11425
11426 route->m_NextLegGreatCircle = false;
11427 int milesDiff = rhumbDist - gcDistm;
11428 if (milesDiff > 1) {
11429 brg = gcBearing;
11430 dist = gcDistm;
11431 route->m_NextLegGreatCircle = true;
11432 }
11433
11434 // FIXME (MacOS, the first segment is rendered wrong)
11435 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11436 &lastPoint);
11437
11438 if (route->m_NextLegGreatCircle) {
11439 for (int i = 1; i <= milesDiff; i++) {
11440 double p = (double)i * (1.0 / (double)milesDiff);
11441 double pLat, pLon;
11442 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11443 &pLon, &pLat, &gcBearing2);
11444 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11445 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11446 false);
11447 lastPoint = destPoint;
11448 }
11449 } else {
11450 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11451 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11452 false);
11453 if (m_bMeasure_DistCircle) {
11454 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11455 powf((float)(r_rband.y - lastPoint.y), 2));
11456
11457 dc.SetPen(*g_pRouteMan->GetRoutePen());
11458 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11459 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11460 }
11461 }
11462 }
11463 }
11464
11465 wxString routeInfo;
11466 double varBrg = 0;
11467 if (g_bShowTrue)
11468 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11469 0x00B0);
11470
11471 if (g_bShowMag) {
11472 double latAverage = (m_cursor_lat + render_lat) / 2;
11473 double lonAverage = (m_cursor_lon + render_lon) / 2;
11474 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11475
11476 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11477 (int)varBrg, 0x00B0);
11478 }
11479 routeInfo << " " << FormatDistanceAdaptive(dist);
11480
11481 // To make it easier to use a route as a bearing on a charted object add for
11482 // the first leg also the reverse bearing.
11483 if (np == 1) {
11484 routeInfo << "\nReverse: ";
11485 if (g_bShowTrue)
11486 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11487 (int)(brg + 180.) % 360, 0x00B0);
11488 if (g_bShowMag)
11489 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11490 (int)(varBrg + 180.) % 360, 0x00B0);
11491 }
11492
11493 wxString s0;
11494 if (!route->m_bIsInLayer)
11495 s0.Append(_("Route") + ": ");
11496 else
11497 s0.Append(_("Layer Route: "));
11498
11499 double disp_length = route->m_route_length;
11500 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11501 s0 += FormatDistanceAdaptive(disp_length);
11502
11503 RouteLegInfo(dc, r_rband, routeInfo, s0);
11504
11505 m_brepaint_piano = true;
11506}
11507
11508void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11509 if (!m_bShowVisibleSectors) return;
11510
11511 if (g_bDeferredInitDone) {
11512 // need to re-evaluate sectors?
11513 double rhumbBearing, rhumbDist;
11514 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11515 &rhumbBearing, &rhumbDist);
11516
11517 if (rhumbDist > 0.05) // miles
11518 {
11519 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11520 m_sectorlegsVisible);
11521 m_sector_glat = gLat;
11522 m_sector_glon = gLon;
11523 }
11524 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11525 }
11526}
11527
11528void ChartCanvas::WarpPointerDeferred(int x, int y) {
11529 warp_x = x;
11530 warp_y = y;
11531 warp_flag = true;
11532}
11533
11534int s_msg;
11535
11536void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11537 if (!ps52plib) return;
11538
11539 if (VPoint.b_quilt) { // quilted
11540 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11541
11542 if (m_pQuilt->IsQuiltVector()) {
11543 if (ps52plib->GetStateHash() != m_s52StateHash) {
11544 UpdateS52State();
11545 m_s52StateHash = ps52plib->GetStateHash();
11546 }
11547 }
11548 } else {
11549 if (ps52plib->GetStateHash() != m_s52StateHash) {
11550 UpdateS52State();
11551 m_s52StateHash = ps52plib->GetStateHash();
11552 }
11553 }
11554
11555 // Plugin charts
11556 bool bSendPlibState = true;
11557 if (VPoint.b_quilt) { // quilted
11558 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11559 }
11560
11561 if (bSendPlibState) {
11562 wxJSONValue v;
11563 v["OpenCPN Version Major"] = VERSION_MAJOR;
11564 v["OpenCPN Version Minor"] = VERSION_MINOR;
11565 v["OpenCPN Version Patch"] = VERSION_PATCH;
11566 v["OpenCPN Version Date"] = VERSION_DATE;
11567 v["OpenCPN Version Full"] = VERSION_FULL;
11568
11569 // S52PLIB state
11570 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11571 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11572 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11573 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11574 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11575 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11576 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11577
11578 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11579
11580 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11581 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11582
11583 // Global S52 options
11584
11585 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11586 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11587 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11588 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11589 ps52plib->m_bShowS57ImportantTextOnly;
11590 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11591 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11592 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11593 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11594 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11595
11596 // Some global GUI parameters, for completeness
11597 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11598 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11599 v["OpenCPN Scale Factor Exp"] =
11600 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11601 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11602
11603 wxJSONWriter w;
11604 wxString out;
11605 w.Write(v, out);
11606
11607 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11608 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11609 g_lastS52PLIBPluginMessage = out;
11610 }
11611 }
11612}
11613int spaint;
11614int s_in_update;
11615void ChartCanvas::OnPaint(wxPaintEvent &event) {
11616 wxPaintDC dc(this);
11617
11618 // GetToolbar()->Show( m_bToolbarEnable );
11619
11620 // Paint updates may have been externally disabled (temporarily, to avoid
11621 // Yield() recursion performance loss) It is important that the wxPaintDC is
11622 // built, even if we elect to not process this paint message. Otherwise, the
11623 // paint message may not be removed from the message queue, esp on Windows.
11624 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11625
11626 if (!m_b_paint_enable) {
11627 return;
11628 }
11629
11630 // If necessary, reconfigure the S52 PLIB
11631 UpdateCanvasS52PLIBConfig();
11632
11633#ifdef ocpnUSE_GL
11634 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11635
11636 if (m_glcc && g_bopengl) {
11637 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11638 s_in_update++;
11639 m_glcc->Update();
11640 s_in_update--;
11641 }
11642
11643 return;
11644 }
11645#endif
11646
11647 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11648
11649 wxRegion ru = GetUpdateRegion();
11650
11651 int rx, ry, rwidth, rheight;
11652 ru.GetBox(rx, ry, rwidth, rheight);
11653 // printf("%d Onpaint update region box: %d %d %d %d\n", spaint++, rx, ry,
11654 // rwidth, rheight);
11655
11656#ifdef ocpnUSE_DIBSECTION
11657 ocpnMemDC temp_dc;
11658#else
11659 wxMemoryDC temp_dc;
11660#endif
11661
11662 long height = GetVP().pix_height;
11663
11664#ifdef __WXMAC__
11665 // On OS X we have to explicitly extend the region for the piano area
11666 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11667 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11668 height += m_Piano->GetHeight();
11669#endif // __WXMAC__
11670 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11671
11672 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11673 if (pthumbwin) {
11674 int thumbx, thumby, thumbsx, thumbsy;
11675 pthumbwin->GetPosition(&thumbx, &thumby);
11676 pthumbwin->GetSize(&thumbsx, &thumbsy);
11677 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11678
11679 if (pthumbwin->IsShown()) {
11680 rgn_chart.Subtract(rgn_thumbwin);
11681 ru.Subtract(rgn_thumbwin);
11682 }
11683 }
11684
11685 // subtract the chart bar if it isn't transparent, and determine if we need to
11686 // paint it
11687 wxRegion rgn_blit = ru;
11688 if (g_bShowChartBar) {
11689 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11690 GetClientSize().x, m_Piano->GetHeight());
11691
11692 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11693 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11694 if (style->chartStatusWindowTransparent)
11695 m_brepaint_piano = true;
11696 else
11697 ru.Subtract(chart_bar_rect);
11698 }
11699 }
11700
11701 if (m_Compass && m_Compass->IsShown()) {
11702 wxRect compassRect = m_Compass->GetRect();
11703 if (ru.Contains(compassRect) != wxOutRegion) {
11704 ru.Subtract(compassRect);
11705 }
11706 }
11707
11708 wxRect noteRect = m_notification_button->GetRect();
11709 if (ru.Contains(noteRect) != wxOutRegion) {
11710 ru.Subtract(noteRect);
11711 }
11712
11713 // Is this viewpoint the same as the previously painted one?
11714 bool b_newview = true;
11715
11716 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11717 (m_cache_vp.rotation == VPoint.rotation) &&
11718 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11719 m_cache_vp.IsValid()) {
11720 b_newview = false;
11721 }
11722
11723 // If the ViewPort is skewed or rotated, we may be able to use the cached
11724 // rotated bitmap.
11725 bool b_rcache_ok = false;
11726 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11727 b_rcache_ok = !b_newview;
11728
11729 // Make a special VP
11730 if (VPoint.b_MercatorProjectionOverride)
11731 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11732 ViewPort svp = VPoint;
11733
11734 svp.pix_width = svp.rv_rect.width;
11735 svp.pix_height = svp.rv_rect.height;
11736
11737 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11738 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11739 // VPoint.rv_rect.height);
11740
11741 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11742
11743 // If we are going to use the cached rotated image, there is no need to fetch
11744 // any chart data and this will do it...
11745 if (b_rcache_ok) chart_get_region.Clear();
11746
11747 // Blit pan acceleration
11748 if (VPoint.b_quilt) // quilted
11749 {
11750 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11751
11752 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11753
11754 bool busy = false;
11755 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11756 m_cache_vp.rotation != VPoint.rotation)) {
11757 AbstractPlatform::ShowBusySpinner();
11758 busy = true;
11759 }
11760
11761 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11762 (m_working_bm.GetHeight() != svp.pix_height))
11763 m_working_bm.Create(svp.pix_width, svp.pix_height,
11764 -1); // make sure the target is big enoug
11765
11766 if (fabs(VPoint.rotation) < 0.01) {
11767 bool b_save = true;
11768
11769 if (g_SencThreadManager) {
11770 if (g_SencThreadManager->GetJobCount()) {
11771 b_save = false;
11772 m_cache_vp.Invalidate();
11773 }
11774 }
11775
11776 // If the saved wxBitmap from last OnPaint is useable
11777 // calculate the blit parameters
11778
11779 // We can only do screen blit painting if subsequent ViewPorts differ by
11780 // whole pixels So, in small scale bFollow mode, force the full screen
11781 // render. This seems a hack....There may be better logic here.....
11782
11783 // if(m_bFollow)
11784 // b_save = false;
11785
11786 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11787 if (b_newview) {
11788 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11789 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11790
11791 int dy = c_new.y - c_old.y;
11792 int dx = c_new.x - c_old.x;
11793
11794 // printf("In OnPaint Trying Blit dx: %d
11795 // dy:%d\n\n", dx, dy);
11796
11797 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11798 if (dx || dy) {
11799 // Blit the reuseable portion of the cached wxBitmap to a working
11800 // bitmap
11801 temp_dc.SelectObject(m_working_bm);
11802
11803 wxMemoryDC cache_dc;
11804 cache_dc.SelectObject(m_cached_chart_bm);
11805
11806 if (dy > 0) {
11807 if (dx > 0) {
11808 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11809 VPoint.pix_height - dy, &cache_dc, dx, dy);
11810 } else {
11811 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11812 VPoint.pix_height - dy, &cache_dc, 0, dy);
11813 }
11814
11815 } else {
11816 if (dx > 0) {
11817 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11818 VPoint.pix_height + dy, &cache_dc, dx, 0);
11819 } else {
11820 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11821 VPoint.pix_height + dy, &cache_dc, 0, 0);
11822 }
11823 }
11824
11825 OCPNRegion update_region;
11826 if (dy) {
11827 if (dy > 0)
11828 update_region.Union(
11829 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11830 else
11831 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11832 }
11833
11834 if (dx) {
11835 if (dx > 0)
11836 update_region.Union(
11837 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11838 else
11839 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11840 }
11841
11842 // Render the new region
11843 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11844 update_region);
11845 cache_dc.SelectObject(wxNullBitmap);
11846 } else {
11847 // No sensible (dx, dy) change in the view, so use the cached
11848 // member bitmap
11849 temp_dc.SelectObject(m_cached_chart_bm);
11850 b_save = false;
11851 }
11852 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11853
11854 } else // not blitable
11855 {
11856 temp_dc.SelectObject(m_working_bm);
11857 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11858 chart_get_region);
11859 }
11860 } else {
11861 // No change in the view, so use the cached member bitmap2
11862 temp_dc.SelectObject(m_cached_chart_bm);
11863 b_save = false;
11864 }
11865 } else // cached bitmap is not yet valid
11866 {
11867 temp_dc.SelectObject(m_working_bm);
11868 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11869 chart_get_region);
11870 }
11871
11872 // Save the fully rendered quilt image as a wxBitmap member of this class
11873 if (b_save) {
11874 // if((m_cached_chart_bm.GetWidth() !=
11875 // svp.pix_width) ||
11876 // (m_cached_chart_bm.GetHeight() !=
11877 // svp.pix_height))
11878 // m_cached_chart_bm.Create(svp.pix_width,
11879 // svp.pix_height, -1); // target wxBitmap
11880 // is big enough
11881 wxMemoryDC scratch_dc_0;
11882 scratch_dc_0.SelectObject(m_cached_chart_bm);
11883 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11884
11885 scratch_dc_0.SelectObject(wxNullBitmap);
11886
11887 m_bm_cache_vp =
11888 VPoint; // save the ViewPort associated with the cached wxBitmap
11889 }
11890 }
11891
11892 else // quilted, rotated
11893 {
11894 temp_dc.SelectObject(m_working_bm);
11895 OCPNRegion chart_get_all_region(
11896 wxRect(0, 0, svp.pix_width, svp.pix_height));
11897 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11898 chart_get_all_region);
11899 }
11900
11901 AbstractPlatform::HideBusySpinner();
11902
11903 }
11904
11905 else // not quilted
11906 {
11907 if (!m_singleChart) {
11908 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
11909 dc.Clear();
11910 return;
11911 }
11912
11913 if (!chart_get_region.IsEmpty()) {
11914 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
11915 }
11916 }
11917
11918 if (temp_dc.IsOk()) {
11919 // Arrange to render the World Chart vector data behind the rendered
11920 // current chart so that uncovered canvas areas show at least the world
11921 // chart.
11922 OCPNRegion chartValidRegion;
11923 if (!VPoint.b_quilt) {
11924 // Make a region covering the current chart on the canvas
11925
11926 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
11927 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
11928 else {
11929 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
11930 // require that the viewport passed here have pix_width and pix_height
11931 // set to the actual display, not the virtual (rv_rect) sizes
11932 // (the vector calculations require the virtual sizes in svp)
11933
11934 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
11935 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
11936 }
11937 } else
11938 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
11939
11940 temp_dc.DestroyClippingRegion();
11941
11942 // Copy current chart region
11943 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
11944
11945 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
11946
11947 if (!backgroundRegion.IsEmpty()) {
11948 // Draw the Background Chart only in the areas NOT covered by the
11949 // current chart view
11950
11951 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
11952 clipping regions with more than 1 rectangle so... */
11953 wxColour water = pWorldBackgroundChart->water;
11954 if (water.IsOk()) {
11955 temp_dc.SetPen(*wxTRANSPARENT_PEN);
11956 temp_dc.SetBrush(wxBrush(water));
11957 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
11958 while (upd.HaveRects()) {
11959 wxRect rect = upd.GetRect();
11960 temp_dc.DrawRectangle(rect);
11961 upd.NextRect();
11962 }
11963 }
11964 // Associate with temp_dc
11965 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
11966 temp_dc.SetDeviceClippingRegion(*clip_region);
11967 delete clip_region;
11968
11969 ocpnDC bgdc(temp_dc);
11970 double r = VPoint.rotation;
11971 SetVPRotation(VPoint.skew);
11972
11973 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
11974 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
11975
11976 SetVPRotation(r);
11977 }
11978 } // temp_dc.IsOk();
11979
11980 wxMemoryDC *pChartDC = &temp_dc;
11981 wxMemoryDC rotd_dc;
11982
11983 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
11984 // Can we use the current rotated image cache?
11985 if (!b_rcache_ok) {
11986#ifdef __WXMSW__
11987 wxMemoryDC tbase_dc;
11988 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
11989 tbase_dc.SelectObject(bm_base);
11990 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11991 tbase_dc.SelectObject(wxNullBitmap);
11992#else
11993 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
11994#endif
11995
11996 wxImage base_image;
11997 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
11998
11999 // Use a local static image rotator to improve wxWidgets code profile
12000 // Especially, on GTK the wxRound and wxRealPoint functions are very
12001 // expensive.....
12002
12003 double angle = GetVP().skew - GetVP().rotation;
12004 wxImage ri;
12005 bool b_rot_ok = false;
12006 if (base_image.IsOk()) {
12007 ViewPort rot_vp = GetVP();
12008
12009 m_b_rot_hidef = false;
12010
12011 ri = Image_Rotate(
12012 base_image, angle,
12013 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12014 m_b_rot_hidef, &m_roffset);
12015
12016 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12017 (rot_vp.rotation == VPoint.rotation) &&
12018 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12019 rot_vp.IsValid() && (ri.IsOk())) {
12020 b_rot_ok = true;
12021 }
12022 }
12023
12024 if (b_rot_ok) {
12025 delete m_prot_bm;
12026 m_prot_bm = new wxBitmap(ri);
12027 }
12028
12029 m_roffset.x += VPoint.rv_rect.x;
12030 m_roffset.y += VPoint.rv_rect.y;
12031 }
12032
12033 if (m_prot_bm && m_prot_bm->IsOk()) {
12034 rotd_dc.SelectObject(*m_prot_bm);
12035 pChartDC = &rotd_dc;
12036 } else {
12037 pChartDC = &temp_dc;
12038 m_roffset = wxPoint(0, 0);
12039 }
12040 } else { // unrotated
12041 pChartDC = &temp_dc;
12042 m_roffset = wxPoint(0, 0);
12043 }
12044
12045 wxPoint offset = m_roffset;
12046
12047 // Save the PixelCache viewpoint for next time
12048 m_cache_vp = VPoint;
12049
12050 // Set up a scratch DC for overlay objects
12051 wxMemoryDC mscratch_dc;
12052 mscratch_dc.SelectObject(*pscratch_bm);
12053
12054 mscratch_dc.ResetBoundingBox();
12055 mscratch_dc.DestroyClippingRegion();
12056 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12057
12058 // Blit the externally invalidated areas of the chart onto the scratch dc
12059 wxRegionIterator upd(rgn_blit); // get the update rect list
12060 while (upd) {
12061 wxRect rect = upd.GetRect();
12062
12063 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12064 rect.x - offset.x, rect.y - offset.y);
12065 upd++;
12066 }
12067
12068 // If multi-canvas, indicate which canvas has keyboard focus
12069 // by drawing a simple blue bar at the top.
12070 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12071 if (this == wxWindow::FindFocus()) {
12072 g_focusCanvas = this;
12073
12074 wxColour colour = GetGlobalColor("BLUE4");
12075 mscratch_dc.SetPen(wxPen(colour));
12076 mscratch_dc.SetBrush(wxBrush(colour));
12077
12078 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12079 mscratch_dc.DrawRectangle(activeRect);
12080 }
12081 }
12082
12083 // Any MBtiles?
12084 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12085 unsigned int im = stackIndexArray.size();
12086 if (VPoint.b_quilt && im > 0) {
12087 std::vector<int> tiles_to_show;
12088 for (unsigned int is = 0; is < im; is++) {
12089 const ChartTableEntry &cte =
12090 ChartData->GetChartTableEntry(stackIndexArray[is]);
12091 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12092 continue;
12093 }
12094 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12095 tiles_to_show.push_back(stackIndexArray[is]);
12096 }
12097 }
12098
12099 if (tiles_to_show.size())
12100 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12101 }
12102
12103 // May get an unexpected OnPaint call while switching display modes
12104 // Guard for that.
12105 if (!g_bopengl) {
12106 ocpnDC scratch_dc(mscratch_dc);
12107 RenderAlertMessage(mscratch_dc, GetVP());
12108 }
12109
12110#if 0
12111 // quiting?
12112 if (g_bquiting) {
12113#ifdef ocpnUSE_DIBSECTION
12114 ocpnMemDC q_dc;
12115#else
12116 wxMemoryDC q_dc;
12117#endif
12118 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12119 q_dc.SelectObject(qbm);
12120
12121 // Get a copy of the screen
12122 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12123
12124 // Draw a rectangle over the screen with a stipple brush
12125 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12126 q_dc.SetBrush(qbr);
12127 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12128
12129 // Blit back into source
12130 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12131 wxCOPY);
12132
12133 q_dc.SelectObject(wxNullBitmap);
12134 }
12135#endif
12136
12137#if 0
12138 // It is possible that this two-step method may be reuired for some platforms.
12139 // So, retain in the code base to aid recovery if necessary
12140
12141 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12142 if( VPoint.b_quilt ) {
12143 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12144 ChartBase *chart = m_pQuilt->GetRefChart();
12145 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12146
12147 // Clear the text Global declutter list
12148 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12149 if(ChPI)
12150 ChPI->ClearPLIBTextList();
12151 else{
12152 if(ps52plib)
12153 ps52plib->ClearTextList();
12154 }
12155
12156 wxMemoryDC t_dc;
12157 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12158
12159 wxColor maskBackground = wxColour(1,0,0);
12160 t_dc.SelectObject( qbm );
12161 t_dc.SetBackground(wxBrush(maskBackground));
12162 t_dc.Clear();
12163
12164 // Copy the scratch DC into the new bitmap
12165 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12166
12167 // Render the text to the new bitmap
12168 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12169 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12170
12171 // Copy the new bitmap back to the scratch dc
12172 wxRegionIterator upd_final( ru );
12173 while( upd_final ) {
12174 wxRect rect = upd_final.GetRect();
12175 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12176 upd_final++;
12177 }
12178
12179 t_dc.SelectObject( wxNullBitmap );
12180 }
12181 }
12182 }
12183#endif
12184 // Direct rendering model...
12185 if (VPoint.b_quilt) {
12186 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12187 ChartBase *chart = m_pQuilt->GetRefChart();
12188 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12189 // Clear the text Global declutter list
12190 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12191 if (ChPI)
12192 ChPI->ClearPLIBTextList();
12193 else {
12194 if (ps52plib) ps52plib->ClearTextList();
12195 }
12196
12197 // Render the text directly to the scratch bitmap
12198 OCPNRegion chart_all_text_region(
12199 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12200
12201 if (g_bShowChartBar && m_Piano) {
12202 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12203 GetVP().pix_width, m_Piano->GetHeight());
12204
12205 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12206 if (!style->chartStatusWindowTransparent)
12207 chart_all_text_region.Subtract(chart_bar_rect);
12208 }
12209
12210 if (m_Compass && m_Compass->IsShown()) {
12211 wxRect compassRect = m_Compass->GetRect();
12212 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12213 chart_all_text_region.Subtract(compassRect);
12214 }
12215 }
12216
12217 mscratch_dc.DestroyClippingRegion();
12218
12219 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12220 chart_all_text_region);
12221 }
12222 }
12223 }
12224
12225 // Now that charts are fully rendered, apply the overlay objects as decals.
12226 ocpnDC scratch_dc(mscratch_dc);
12227 DrawOverlayObjects(scratch_dc, ru);
12228
12229 // And finally, blit the scratch dc onto the physical dc
12230 wxRegionIterator upd_final(rgn_blit);
12231 while (upd_final) {
12232 wxRect rect = upd_final.GetRect();
12233 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12234 rect.y);
12235 upd_final++;
12236 }
12237
12238 // Deselect the chart bitmap from the temp_dc, so that it will not be
12239 // destroyed in the temp_dc dtor
12240 temp_dc.SelectObject(wxNullBitmap);
12241 // And for the scratch bitmap
12242 mscratch_dc.SelectObject(wxNullBitmap);
12243
12244 dc.DestroyClippingRegion();
12245
12246 PaintCleanup();
12247}
12248
12249void ChartCanvas::PaintCleanup() {
12250 // Handle the current graphic window, if present
12251
12252 if (pCwin) {
12253 pCwin->Show();
12254 if (m_bTCupdate) {
12255 pCwin->Refresh();
12256 pCwin->Update();
12257 }
12258 }
12259
12260 // And set flags for next time
12261 m_bTCupdate = false;
12262
12263 // Handle deferred WarpPointer
12264 if (warp_flag) {
12265 WarpPointer(warp_x, warp_y);
12266 warp_flag = false;
12267 }
12268
12269 // Start movement timers, this runs nearly immediately.
12270 // the reason we cannot simply call it directly is the
12271 // refresh events it emits may be blocked from this paint event
12272 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12273 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12274}
12275
12276#if 0
12277wxColour GetErrorGraphicColor(double val)
12278{
12279 /*
12280 double valm = wxMin(val_max, val);
12281
12282 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12283 unsigned char red = (unsigned char)(255 * (valm/val_max));
12284
12285 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12286
12287 hv.saturation = 1.0;
12288 hv.value = 1.0;
12289
12290 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12291 return wxColour(rv.red, rv.green, rv.blue);
12292 */
12293
12294 // HTML colors taken from NOAA WW3 Web representation
12295 wxColour c;
12296 if((val > 0) && (val < 1)) c.Set("#002ad9");
12297 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12298 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12299 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12300 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12301 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12302 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12303 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12304 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12305 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12306 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12307 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12308 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12309 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12310 else if((val >= 30) && (val < 36)) c.Set("#870000");
12311 else if((val >= 36) && (val < 42)) c.Set("#690000");
12312 else if((val >= 42) && (val < 48)) c.Set("#550000");
12313 else if( val >= 48) c.Set("#410000");
12314
12315 return c;
12316}
12317
12318void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12319{
12320 wxImage gr_image(vp->pix_width, vp->pix_height);
12321 gr_image.InitAlpha();
12322
12323 double maxval = -10000;
12324 double minval = 10000;
12325
12326 double rlat, rlon;
12327 double glat, glon;
12328
12329 GetCanvasPixPoint(0, 0, rlat, rlon);
12330
12331 for(int i=1; i < vp->pix_height-1; i++)
12332 {
12333 for(int j=0; j < vp->pix_width; j++)
12334 {
12335 // Reference mercator value
12336// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12337
12338 // Georef value
12339 GetCanvasPixPoint(j, i, glat, glon);
12340
12341 maxval = wxMax(maxval, (glat - rlat));
12342 minval = wxMin(minval, (glat - rlat));
12343
12344 }
12345 rlat = glat;
12346 }
12347
12348 GetCanvasPixPoint(0, 0, rlat, rlon);
12349 for(int i=1; i < vp->pix_height-1; i++)
12350 {
12351 for(int j=0; j < vp->pix_width; j++)
12352 {
12353 // Reference mercator value
12354// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12355
12356 // Georef value
12357 GetCanvasPixPoint(j, i, glat, glon);
12358
12359 double f = ((glat - rlat)-minval)/(maxval - minval);
12360
12361 double dy = (f * 40);
12362
12363 wxColour c = GetErrorGraphicColor(dy);
12364 unsigned char r = c.Red();
12365 unsigned char g = c.Green();
12366 unsigned char b = c.Blue();
12367
12368 gr_image.SetRGB(j, i, r,g,b);
12369 if((glat - rlat )!= 0)
12370 gr_image.SetAlpha(j, i, 128);
12371 else
12372 gr_image.SetAlpha(j, i, 255);
12373
12374 }
12375 rlat = glat;
12376 }
12377
12378 // Create a Bitmap
12379 wxBitmap *pbm = new wxBitmap(gr_image);
12380 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12381 pbm->SetMask(gr_mask);
12382
12383 pmdc->DrawBitmap(*pbm, 0,0);
12384
12385 delete pbm;
12386
12387}
12388
12389#endif
12390
12391void ChartCanvas::CancelMouseRoute() {
12392 m_routeState = 0;
12393 m_pMouseRoute = NULL;
12394 m_bDrawingRoute = false;
12395}
12396
12397int ChartCanvas::GetNextContextMenuId() {
12398 return CanvasMenuHandler::GetNextContextMenuId();
12399}
12400
12401bool ChartCanvas::SetCursor(const wxCursor &c) {
12402#ifdef ocpnUSE_GL
12403 if (g_bopengl && m_glcc)
12404 return m_glcc->SetCursor(c);
12405 else
12406#endif
12407 return wxWindow::SetCursor(c);
12408}
12409
12410void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12411 if (g_bquiting) return;
12412 // Keep the mouse position members up to date
12413 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12414
12415 // Retrigger the route leg popup timer
12416 // This handles the case when the chart is moving in auto-follow mode,
12417 // but no user mouse input is made. The timer handler may Hide() the
12418 // popup if the chart moved enough n.b. We use slightly longer oneshot
12419 // value to allow this method's Refresh() to complete before potentially
12420 // getting another Refresh() in the popup timer handler.
12421 if (!m_RolloverPopupTimer.IsRunning() &&
12422 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12423 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12424 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12425 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12426
12427#ifdef ocpnUSE_GL
12428 if (m_glcc && g_bopengl) {
12429 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12430 // overlay objects.
12431 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12432
12433 m_glcc->Refresh(eraseBackground,
12434 NULL); // We always are going to render the entire screen
12435 // anyway, so make
12436 // sure that the window managers understand the invalid area
12437 // is actually the entire client area.
12438
12439 // We need to selectively Refresh some child windows, if they are visible.
12440 // Note that some children are refreshed elsewhere on timer ticks, so don't
12441 // need attention here.
12442
12443 // Thumbnail chart
12444 if (pthumbwin && pthumbwin->IsShown()) {
12445 pthumbwin->Raise();
12446 pthumbwin->Refresh(false);
12447 }
12448
12449 // ChartInfo window
12450 if (m_pCIWin && m_pCIWin->IsShown()) {
12451 m_pCIWin->Raise();
12452 m_pCIWin->Refresh(false);
12453 }
12454
12455 // if(g_MainToolbar)
12456 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12457
12458 } else
12459#endif
12460 wxWindow::Refresh(eraseBackground, rect);
12461}
12462
12463void ChartCanvas::Update() {
12464 if (m_glcc && g_bopengl) {
12465#ifdef ocpnUSE_GL
12466 m_glcc->Update();
12467#endif
12468 } else
12469 wxWindow::Update();
12470}
12471
12472void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12473 if (!pemboss) return;
12474 int x = pemboss->x, y = pemboss->y;
12475 const double factor = 200;
12476
12477 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12478 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12479 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12480
12481 // Grab a snipped image out of the chart
12482 wxMemoryDC snip_dc;
12483 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12484 snip_dc.SelectObject(snip_bmp);
12485
12486 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12487 snip_dc.SelectObject(wxNullBitmap);
12488
12489 wxImage snip_img = snip_bmp.ConvertToImage();
12490
12491 // Apply Emboss map to the snip image
12492 unsigned char *pdata = snip_img.GetData();
12493 if (pdata) {
12494 for (int y = 0; y < pemboss->height; y++) {
12495 int map_index = (y * pemboss->width);
12496 for (int x = 0; x < pemboss->width; x++) {
12497 double val = (pemboss->pmap[map_index] * factor) / 256.;
12498
12499 int nred = (int)((*pdata) + val);
12500 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12501 *pdata++ = (unsigned char)nred;
12502
12503 int ngreen = (int)((*pdata) + val);
12504 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12505 *pdata++ = (unsigned char)ngreen;
12506
12507 int nblue = (int)((*pdata) + val);
12508 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12509 *pdata++ = (unsigned char)nblue;
12510
12511 map_index++;
12512 }
12513 }
12514 }
12515
12516 // Convert embossed snip to a bitmap
12517 wxBitmap emb_bmp(snip_img);
12518
12519 // Map to another memoryDC
12520 wxMemoryDC result_dc;
12521 result_dc.SelectObject(emb_bmp);
12522
12523 // Blit to target
12524 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12525
12526 result_dc.SelectObject(wxNullBitmap);
12527}
12528
12529emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12530 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12531
12532 if (GetQuiltMode()) {
12533 // disable Overzoom indicator for MBTiles
12534 int refIndex = GetQuiltRefChartdbIndex();
12535 if (refIndex >= 0) {
12536 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12537 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12538 if (current_type == CHART_TYPE_MBTILES) {
12539 ChartBase *pChart = m_pQuilt->GetRefChart();
12540 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12541 if (ptc) {
12542 zoom_factor = ptc->GetZoomFactor();
12543 }
12544 }
12545 }
12546
12547 if (zoom_factor <= 3.9) return NULL;
12548 } else {
12549 if (m_singleChart) {
12550 if (zoom_factor <= 3.9) return NULL;
12551 } else
12552 return NULL;
12553 }
12554
12555 if (m_pEM_OverZoom) {
12556 m_pEM_OverZoom->x = 4;
12557 m_pEM_OverZoom->y = 0;
12558 if (g_MainToolbar && IsPrimaryCanvas()) {
12559 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12560 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12561 }
12562 }
12563 return m_pEM_OverZoom;
12564}
12565
12566void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12567 GridDraw(dc);
12568
12569 // bool pluginOverlayRender = true;
12570 //
12571 // if(g_canvasConfig > 0){ // Multi canvas
12572 // if(IsPrimaryCanvas())
12573 // pluginOverlayRender = false;
12574 // }
12575
12576 g_overlayCanvas = this;
12577
12578 if (g_pi_manager) {
12579 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12580 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12582 }
12583
12584 AISDrawAreaNotices(dc, GetVP(), this);
12585
12586 wxDC *pdc = dc.GetDC();
12587 if (pdc) {
12588 pdc->DestroyClippingRegion();
12589 wxDCClipper(*pdc, ru);
12590 }
12591
12592 if (m_bShowNavobjects) {
12593 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12594 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12595 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12596 DrawAnchorWatchPoints(dc);
12597 } else {
12598 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12599 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12600 }
12601
12602 AISDraw(dc, GetVP(), this);
12603 ShipDraw(dc);
12604 AlertDraw(dc);
12605
12606 RenderVisibleSectorLights(dc);
12607
12608 RenderAllChartOutlines(dc, GetVP());
12609 RenderRouteLegs(dc);
12610 RenderShipToActive(dc, false);
12611 ScaleBarDraw(dc);
12612 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12613 if (g_pi_manager) {
12614 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12616 }
12617
12618 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12619 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12620
12621 if (g_pi_manager) {
12622 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12624 }
12625
12626 if (m_bShowTide) {
12627 RebuildTideSelectList(GetVP().GetBBox());
12628 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12629 }
12630
12631 if (m_bShowCurrent) {
12632 RebuildCurrentSelectList(GetVP().GetBBox());
12633 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12634 }
12635
12636 if (!g_PrintingInProgress) {
12637 if (IsPrimaryCanvas()) {
12638 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12639 }
12640
12641 if (IsPrimaryCanvas()) {
12642 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12643 }
12644
12645 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12646
12647 if (m_pTrackRolloverWin) {
12648 m_pTrackRolloverWin->Draw(dc);
12649 m_brepaint_piano = true;
12650 }
12651
12652 if (m_pRouteRolloverWin) {
12653 m_pRouteRolloverWin->Draw(dc);
12654 m_brepaint_piano = true;
12655 }
12656
12657 if (m_pAISRolloverWin) {
12658 m_pAISRolloverWin->Draw(dc);
12659 m_brepaint_piano = true;
12660 }
12661 if (m_brepaint_piano && g_bShowChartBar) {
12662 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12663 }
12664
12665 if (m_Compass) m_Compass->Paint(dc);
12666
12667 if (!g_CanvasHideNotificationIcon) {
12668 auto &noteman = NotificationManager::GetInstance();
12669 if (noteman.GetNotificationCount()) {
12670 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12671 if (m_notification_button->UpdateStatus()) Refresh();
12672 m_notification_button->Show(true);
12673 m_notification_button->Paint(dc);
12674 } else {
12675 m_notification_button->Show(false);
12676 }
12677 }
12678 }
12679 if (g_pi_manager) {
12680 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12682 }
12683}
12684
12685emboss_data *ChartCanvas::EmbossDepthScale() {
12686 if (!m_bShowDepthUnits) return NULL;
12687
12688 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12689
12690 if (GetQuiltMode()) {
12691 wxString s = m_pQuilt->GetQuiltDepthUnit();
12692 s.MakeUpper();
12693 if (s == "FEET")
12694 depth_unit_type = DEPTH_UNIT_FEET;
12695 else if (s.StartsWith("FATHOMS"))
12696 depth_unit_type = DEPTH_UNIT_FATHOMS;
12697 else if (s.StartsWith("METERS"))
12698 depth_unit_type = DEPTH_UNIT_METERS;
12699 else if (s.StartsWith("METRES"))
12700 depth_unit_type = DEPTH_UNIT_METERS;
12701 else if (s.StartsWith("METRIC"))
12702 depth_unit_type = DEPTH_UNIT_METERS;
12703 else if (s.StartsWith("METER"))
12704 depth_unit_type = DEPTH_UNIT_METERS;
12705
12706 } else {
12707 if (m_singleChart) {
12708 depth_unit_type = m_singleChart->GetDepthUnitType();
12709 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12710 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12711 }
12712 }
12713
12714 emboss_data *ped = NULL;
12715 switch (depth_unit_type) {
12716 case DEPTH_UNIT_FEET:
12717 ped = m_pEM_Feet;
12718 break;
12719 case DEPTH_UNIT_METERS:
12720 ped = m_pEM_Meters;
12721 break;
12722 case DEPTH_UNIT_FATHOMS:
12723 ped = m_pEM_Fathoms;
12724 break;
12725 default:
12726 return NULL;
12727 }
12728
12729 ped->x = (GetVP().pix_width - ped->width);
12730
12731 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12732 wxRect r = m_Compass->GetRect();
12733 ped->y = r.y + r.height;
12734 } else {
12735 ped->y = 40;
12736 }
12737 return ped;
12738}
12739
12740void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12741 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12742 wxFont font;
12743 if (style->embossFont == wxEmptyString) {
12744 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12745 font = *dFont;
12746 font.SetPointSize(60);
12747 font.SetWeight(wxFONTWEIGHT_BOLD);
12748 } else
12749 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12750 wxFONTWEIGHT_BOLD, false, style->embossFont);
12751
12752 int emboss_width = 500;
12753 int emboss_height = 200;
12754
12755 // Free any existing emboss maps
12756 delete m_pEM_Feet;
12757 delete m_pEM_Meters;
12758 delete m_pEM_Fathoms;
12759
12760 // Create the 3 DepthUnit emboss map structures
12761 m_pEM_Feet =
12762 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12763 m_pEM_Meters =
12764 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12765 m_pEM_Fathoms =
12766 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12767}
12768
12769#define OVERZOOM_TEXT _("OverZoom")
12770
12771void ChartCanvas::SetOverzoomFont() {
12772 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12773 int w, h;
12774
12775 wxFont font;
12776 if (style->embossFont == wxEmptyString) {
12777 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12778 font = *dFont;
12779 font.SetPointSize(40);
12780 font.SetWeight(wxFONTWEIGHT_BOLD);
12781 } else
12782 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12783 wxFONTWEIGHT_BOLD, false, style->embossFont);
12784
12785 wxClientDC dc(this);
12786 dc.SetFont(font);
12787 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12788
12789 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12790 font.SetPointSize(font.GetPointSize() - 1);
12791 dc.SetFont(font);
12792 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12793 }
12794 m_overzoomFont = font;
12795 m_overzoomTextWidth = w;
12796 m_overzoomTextHeight = h;
12797}
12798
12799void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12800 delete m_pEM_OverZoom;
12801
12802 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12803 m_pEM_OverZoom =
12804 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12805 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12806}
12807
12808emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12809 int height, const wxString &str,
12810 ColorScheme cs) {
12811 int *pmap;
12812
12813 // Create a temporary bitmap
12814 wxBitmap bmp(width, height, -1);
12815
12816 // Create a memory DC
12817 wxMemoryDC temp_dc;
12818 temp_dc.SelectObject(bmp);
12819
12820 // Paint on it
12821 temp_dc.SetBackground(*wxWHITE_BRUSH);
12822 temp_dc.SetTextBackground(*wxWHITE);
12823 temp_dc.SetTextForeground(*wxBLACK);
12824
12825 temp_dc.Clear();
12826
12827 temp_dc.SetFont(font);
12828
12829 int str_w, str_h;
12830 temp_dc.GetTextExtent(str, &str_w, &str_h);
12831 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12832 temp_dc.DrawText(str, 1, 1);
12833
12834 // Deselect the bitmap
12835 temp_dc.SelectObject(wxNullBitmap);
12836
12837 // Convert bitmap the wxImage for manipulation
12838 wxImage img = bmp.ConvertToImage();
12839
12840 int image_width = str_w * 105 / 100;
12841 int image_height = str_h * 105 / 100;
12842 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12843 wxMin(image_height, img.GetHeight()));
12844 wxImage imgs = img.GetSubImage(r);
12845
12846 double val_factor;
12847 switch (cs) {
12848 case GLOBAL_COLOR_SCHEME_DAY:
12849 default:
12850 val_factor = 1;
12851 break;
12852 case GLOBAL_COLOR_SCHEME_DUSK:
12853 val_factor = .5;
12854 break;
12855 case GLOBAL_COLOR_SCHEME_NIGHT:
12856 val_factor = .25;
12857 break;
12858 }
12859
12860 int val;
12861 int index;
12862 const int w = imgs.GetWidth();
12863 const int h = imgs.GetHeight();
12864 pmap = (int *)calloc(w * h * sizeof(int), 1);
12865 // Create emboss map by differentiating the emboss image
12866 // and storing integer results in pmap
12867 // n.b. since the image is B/W, it is sufficient to check
12868 // one channel (i.e. red) only
12869 for (int y = 1; y < h - 1; y++) {
12870 for (int x = 1; x < w - 1; x++) {
12871 val =
12872 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
12873 val = (int)(val * val_factor);
12874 index = (y * w) + x;
12875 pmap[index] = val;
12876 }
12877 }
12878
12879 emboss_data *pret = new emboss_data;
12880 pret->pmap = pmap;
12881 pret->width = w;
12882 pret->height = h;
12883
12884 return pret;
12885}
12886
12887void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12888 Track *active_track = NULL;
12889 for (Track *pTrackDraw : g_TrackList) {
12890 if (g_pActiveTrack == pTrackDraw) {
12891 active_track = pTrackDraw;
12892 continue;
12893 }
12894
12895 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
12896 }
12897
12898 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12899}
12900
12901void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12902 Track *active_track = NULL;
12903 for (Track *pTrackDraw : g_TrackList) {
12904 if (g_pActiveTrack == pTrackDraw) {
12905 active_track = pTrackDraw;
12906 break;
12907 }
12908 }
12909 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12910}
12911
12912void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12913 Route *active_route = NULL;
12914
12915 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12916 node = node->GetNext()) {
12917 Route *pRouteDraw = node->GetData();
12918 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12919 active_route = pRouteDraw;
12920 continue;
12921 }
12922
12923 // if(m_canvasIndex == 1)
12924 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
12925 }
12926
12927 // Draw any active or selected route (or track) last, so that is is always on
12928 // top
12929 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12930}
12931
12932void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12933 Route *active_route = NULL;
12934
12935 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12936 node = node->GetNext()) {
12937 Route *pRouteDraw = node->GetData();
12938 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12939 active_route = pRouteDraw;
12940 break;
12941 }
12942 }
12943 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12944}
12945
12946void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12947 if (!pWayPointMan) return;
12948
12949 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12950
12951 while (node) {
12952 RoutePoint *pWP = node->GetData();
12953 if (pWP) {
12954 if (pWP->m_bIsInRoute) {
12955 node = node->GetNext();
12956 continue;
12957 }
12958
12959 /* technically incorrect... waypoint has bounding box */
12960 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
12961 RoutePointGui(*pWP).Draw(dc, this, NULL);
12962 else {
12963 // Are Range Rings enabled?
12964 if (pWP->GetShowWaypointRangeRings() &&
12965 (pWP->GetWaypointRangeRingsNumber() > 0)) {
12966 double factor = 1.00;
12967 if (pWP->GetWaypointRangeRingsStepUnits() ==
12968 1) // convert kilometers to NMi
12969 factor = 1 / 1.852;
12970
12971 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
12972 pWP->GetWaypointRangeRingsStep() / 60.;
12973 radius *= 2; // Fudge factor
12974
12975 LLBBox radar_box;
12976 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
12977 pWP->m_lat + radius, pWP->m_lon + radius);
12978 if (!BltBBox.IntersectOut(radar_box)) {
12979 RoutePointGui(*pWP).Draw(dc, this, NULL);
12980 }
12981 }
12982 }
12983 }
12984
12985 node = node->GetNext();
12986 }
12987}
12988
12989void ChartCanvas::DrawBlinkObjects(void) {
12990 // All RoutePoints
12991 wxRect update_rect;
12992
12993 if (!pWayPointMan) return;
12994
12995 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12996
12997 while (node) {
12998 RoutePoint *pWP = node->GetData();
12999 if (pWP) {
13000 if (pWP->m_bBlink) {
13001 update_rect.Union(pWP->CurrentRect_in_DC);
13002 }
13003 }
13004
13005 node = node->GetNext();
13006 }
13007 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13008}
13009
13010void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13011 // draw anchor watch rings, if activated
13012
13013 if (pAnchorWatchPoint1 || pAnchorWatchPoint2) {
13014 wxPoint r1, r2;
13015 wxPoint lAnchorPoint1, lAnchorPoint2;
13016 double lpp1 = 0.0;
13017 double lpp2 = 0.0;
13018 if (pAnchorWatchPoint1) {
13019 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13020 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
13021 &lAnchorPoint1);
13022 }
13023 if (pAnchorWatchPoint2) {
13024 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13025 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
13026 &lAnchorPoint2);
13027 }
13028
13029 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13030 wxPen ppPenr(GetGlobalColor("URED"), 2);
13031
13032 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13033 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13034 dc.SetBrush(*ppBrush);
13035
13036 if (lpp1 > 0) {
13037 dc.SetPen(ppPeng);
13038 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13039 }
13040
13041 if (lpp2 > 0) {
13042 dc.SetPen(ppPeng);
13043 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13044 }
13045
13046 if (lpp1 < 0) {
13047 dc.SetPen(ppPenr);
13048 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13049 }
13050
13051 if (lpp2 < 0) {
13052 dc.SetPen(ppPenr);
13053 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13054 }
13055 }
13056}
13057
13058double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13059 double lpp = 0.;
13060 wxPoint r1;
13061 wxPoint lAnchorPoint;
13062 double d1 = 0.0;
13063 double dabs;
13064 double tlat1, tlon1;
13065
13066 if (pAnchorWatchPoint) {
13067 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13068 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13069 dabs = fabs(d1 / 1852.);
13070 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13071 &tlat1, &tlon1);
13072 GetCanvasPointPix(tlat1, tlon1, &r1);
13073 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13074 &lAnchorPoint);
13075 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13076 pow((double)(lAnchorPoint.y - r1.y), 2));
13077
13078 // This is an entry watch
13079 if (d1 < 0) lpp = -lpp;
13080 }
13081 return lpp;
13082}
13083
13084//------------------------------------------------------------------------------------------
13085// Tides Support
13086//------------------------------------------------------------------------------------------
13087void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13088 if (!ptcmgr) return;
13089
13090 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13091
13092 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13093 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13094 double lon = pIDX->IDX_lon;
13095 double lat = pIDX->IDX_lat;
13096
13097 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13098 if ((type == 't') || (type == 'T')) {
13099 if (BBox.Contains(lat, lon)) {
13100 // Manage the point selection list
13101 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13102 }
13103 }
13104 }
13105}
13106
13107void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13108 if (!ptcmgr) return;
13109
13110 wxDateTime this_now = gTimeSource;
13111 bool cur_time = !gTimeSource.IsValid();
13112 if (cur_time) this_now = wxDateTime::Now();
13113 time_t t_this_now = this_now.GetTicks();
13114
13115 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13116 wxPENSTYLE_SOLID);
13117 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13118 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13119 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13120 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13121
13122 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13123 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13124 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13125 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13126 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13127 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13128
13129 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13130 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13131 int font_size = wxMax(10, dFont->GetPointSize());
13132 font_size /= g_Platform->GetDisplayDIPMult(this);
13133 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13134 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13135 false, dFont->GetFaceName());
13136
13137 dc.SetPen(*pblack_pen);
13138 dc.SetBrush(*pgreen_brush);
13139
13140 wxBitmap bm;
13141 switch (m_cs) {
13142 case GLOBAL_COLOR_SCHEME_DAY:
13143 bm = m_bmTideDay;
13144 break;
13145 case GLOBAL_COLOR_SCHEME_DUSK:
13146 bm = m_bmTideDusk;
13147 break;
13148 case GLOBAL_COLOR_SCHEME_NIGHT:
13149 bm = m_bmTideNight;
13150 break;
13151 default:
13152 bm = m_bmTideDay;
13153 break;
13154 }
13155
13156 int bmw = bm.GetWidth();
13157 int bmh = bm.GetHeight();
13158
13159 float scale_factor = 1.0;
13160
13161 // Set the onscreen size of the symbol
13162 // Compensate for various display resolutions
13163 float icon_pixelRefDim = 45;
13164
13165 // Tidal report graphic is scaled by the text size of the label in use
13166 wxScreenDC sdc;
13167 int height;
13168 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13169 height *= g_Platform->GetDisplayDIPMult(this);
13170 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13171
13172 scale_factor *= pix_factor;
13173
13174 float user_scale_factor = g_ChartScaleFactorExp;
13175 if (g_ChartScaleFactorExp > 1.0)
13176 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13177 1.2; // soften the scale factor a bit
13178
13179 scale_factor *= user_scale_factor;
13180 scale_factor *= GetContentScaleFactor();
13181
13182 {
13183 double marge = 0.05;
13184 std::vector<LLBBox> drawn_boxes;
13185 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13186 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13187
13188 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13189 if ((type == 't') || (type == 'T')) // only Tides
13190 {
13191 double lon = pIDX->IDX_lon;
13192 double lat = pIDX->IDX_lat;
13193
13194 if (BBox.ContainsMarge(lat, lon, marge)) {
13195 // Avoid drawing detailed graphic for duplicate tide stations
13196 if (GetVP().chart_scale < 500000) {
13197 bool bdrawn = false;
13198 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13199 if (drawn_boxes[i].Contains(lat, lon)) {
13200 bdrawn = true;
13201 break;
13202 }
13203 }
13204 if (bdrawn) continue; // the station loop
13205
13206 LLBBox this_box;
13207 this_box.Set(lat, lon, lat, lon);
13208 this_box.EnLarge(.005);
13209 drawn_boxes.push_back(this_box);
13210 }
13211
13212 wxPoint r;
13213 GetCanvasPointPix(lat, lon, &r);
13214 // draw standard icons
13215 if (GetVP().chart_scale > 500000) {
13216 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13217 }
13218 // draw "extended" icons
13219 else {
13220 dc.SetFont(*plabelFont);
13221 {
13222 {
13223 float val, nowlev;
13224 float ltleve = 0.;
13225 float htleve = 0.;
13226 time_t tctime;
13227 time_t lttime = 0;
13228 time_t httime = 0;
13229 bool wt;
13230 // define if flood or ebb in the last ten minutes and verify if
13231 // data are useable
13232 if (ptcmgr->GetTideFlowSens(
13233 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13234 pIDX->IDX_rec_num, nowlev, val, wt)) {
13235 // search forward the first HW or LW near "now" ( starting at
13236 // "now" - ten minutes )
13237 ptcmgr->GetHightOrLowTide(
13238 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13239 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13240 wt, pIDX->IDX_rec_num, val, tctime);
13241 if (wt) {
13242 httime = tctime;
13243 htleve = val;
13244 } else {
13245 lttime = tctime;
13246 ltleve = val;
13247 }
13248 wt = !wt;
13249
13250 // then search opposite tide near "now"
13251 if (tctime > t_this_now) // search backward
13252 ptcmgr->GetHightOrLowTide(
13253 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13254 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13255 pIDX->IDX_rec_num, val, tctime);
13256 else
13257 // or search forward
13258 ptcmgr->GetHightOrLowTide(
13259 t_this_now, FORWARD_TEN_MINUTES_STEP,
13260 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13261 val, tctime);
13262 if (wt) {
13263 httime = tctime;
13264 htleve = val;
13265 } else {
13266 lttime = tctime;
13267 ltleve = val;
13268 }
13269
13270 // draw the tide rectangle:
13271
13272 // tide icon rectangle has default pre-scaled width = 12 ,
13273 // height = 45
13274 int width = (int)(12 * scale_factor + 0.5);
13275 int height = (int)(45 * scale_factor + 0.5);
13276 int linew = wxMax(1, (int)(scale_factor));
13277 int xDraw = r.x - (width / 2);
13278 int yDraw = r.y - (height / 2);
13279
13280 // process tide state ( %height and flow sens )
13281 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13282 int hs = (httime > lttime) ? -4 : 4;
13283 hs *= (int)(scale_factor + 0.5);
13284 if (ts > 0.995 || ts < 0.005) hs = 0;
13285 int ht_y = (int)(height * ts);
13286
13287 // draw yellow tide rectangle outlined in black
13288 pblack_pen->SetWidth(linew);
13289 dc.SetPen(*pblack_pen);
13290 dc.SetBrush(*pyelo_brush);
13291 dc.DrawRectangle(xDraw, yDraw, width, height);
13292
13293 // draw blue rectangle as water height, smaller in width than
13294 // yellow rectangle
13295 dc.SetPen(*pblue_pen);
13296 dc.SetBrush(*pblue_brush);
13297 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13298 (width - (4 * linew)), height - ht_y);
13299
13300 // draw sens arrows (ensure they are not "under-drawn" by top
13301 // line of blue rectangle )
13302 int hl;
13303 wxPoint arrow[3];
13304 arrow[0].x = xDraw + 2 * linew;
13305 arrow[1].x = xDraw + width / 2;
13306 arrow[2].x = xDraw + width - 2 * linew;
13307 pyelo_pen->SetWidth(linew);
13308 pblue_pen->SetWidth(linew);
13309 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13310 {
13311 hl = (int)(height * 0.25) + yDraw;
13312 arrow[0].y = hl;
13313 arrow[1].y = hl + hs;
13314 arrow[2].y = hl;
13315 if (ts < 0.15)
13316 dc.SetPen(*pyelo_pen);
13317 else
13318 dc.SetPen(*pblue_pen);
13319 dc.DrawLines(3, arrow);
13320 }
13321 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13322 {
13323 hl = (int)(height * 0.5) + yDraw;
13324 arrow[0].y = hl;
13325 arrow[1].y = hl + hs;
13326 arrow[2].y = hl;
13327 if (ts < 0.40)
13328 dc.SetPen(*pyelo_pen);
13329 else
13330 dc.SetPen(*pblue_pen);
13331 dc.DrawLines(3, arrow);
13332 }
13333 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13334 {
13335 hl = (int)(height * 0.75) + yDraw;
13336 arrow[0].y = hl;
13337 arrow[1].y = hl + hs;
13338 arrow[2].y = hl;
13339 if (ts < 0.65)
13340 dc.SetPen(*pyelo_pen);
13341 else
13342 dc.SetPen(*pblue_pen);
13343 dc.DrawLines(3, arrow);
13344 }
13345 // draw tide level text
13346 wxString s;
13347 s.Printf("%3.1f", nowlev);
13348 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13349 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13350 int wx1;
13351 dc.GetTextExtent(s, &wx1, NULL);
13352 wx1 *= g_Platform->GetDisplayDIPMult(this);
13353 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13354 }
13355 }
13356 }
13357 }
13358 }
13359 }
13360 }
13361 }
13362}
13363
13364//------------------------------------------------------------------------------------------
13365// Currents Support
13366//------------------------------------------------------------------------------------------
13367
13368void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13369 if (!ptcmgr) return;
13370
13371 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13372
13373 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13374 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13375 double lon = pIDX->IDX_lon;
13376 double lat = pIDX->IDX_lat;
13377
13378 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13379 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13380 if ((BBox.Contains(lat, lon))) {
13381 // Manage the point selection list
13382 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13383 }
13384 }
13385 }
13386}
13387
13388void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13389 if (!ptcmgr) return;
13390
13391 float tcvalue, dir;
13392 bool bnew_val;
13393 char sbuf[20];
13394 wxFont *pTCFont;
13395 double lon_last = 0.;
13396 double lat_last = 0.;
13397 // arrow size for Raz Blanchard : 12 knots north
13398 double marge = 0.2;
13399 bool cur_time = !gTimeSource.IsValid();
13400
13401 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13402 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13403
13404 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13405 wxPENSTYLE_SOLID);
13406 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13407 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13408 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13409 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13410 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13411 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13412 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13413 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13414
13415 double skew_angle = GetVPRotation();
13416
13417 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13418 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13419 int font_size = wxMax(10, dFont->GetPointSize());
13420 font_size /= g_Platform->GetDisplayDIPMult(this);
13421 pTCFont = FontMgr::Get().FindOrCreateFont(
13422 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13423 false, dFont->GetFaceName());
13424
13425 float scale_factor = 1.0;
13426
13427 // Set the onscreen size of the symbol
13428 // Current report graphic is scaled by the text size of the label in use
13429 wxScreenDC sdc;
13430 int height;
13431 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13432 height *= g_Platform->GetDisplayDIPMult(this);
13433 float nominal_icon_size_pixels = 15;
13434 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13435
13436 scale_factor *= pix_factor;
13437
13438 float user_scale_factor = g_ChartScaleFactorExp;
13439 if (g_ChartScaleFactorExp > 1.0)
13440 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13441 1.2; // soften the scale factor a bit
13442
13443 scale_factor *= user_scale_factor;
13444
13445 scale_factor *= GetContentScaleFactor();
13446
13447 {
13448 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13449 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13450 double lon = pIDX->IDX_lon;
13451 double lat = pIDX->IDX_lat;
13452
13453 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13454 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13455 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13456 wxPoint r;
13457 GetCanvasPointPix(lat, lon, &r);
13458
13459 wxPoint d[4]; // points of a diamond at the current station location
13460 int dd = (int)(5.0 * scale_factor + 0.5);
13461 d[0].x = r.x;
13462 d[0].y = r.y + dd;
13463 d[1].x = r.x + dd;
13464 d[1].y = r.y;
13465 d[2].x = r.x;
13466 d[2].y = r.y - dd;
13467 d[3].x = r.x - dd;
13468 d[3].y = r.y;
13469
13470 if (1) {
13471 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13472 dc.SetPen(*pblack_pen);
13473 dc.SetBrush(*porange_brush);
13474 dc.DrawPolygon(4, d);
13475
13476 if (type == 'C') {
13477 dc.SetBrush(*pblack_brush);
13478 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13479 }
13480
13481 if (GetVP().chart_scale < 1000000) {
13482 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13483 continue;
13484 } else
13485 continue;
13486
13487 if (1 /*type == 'c'*/) {
13488 {
13489 // Get the display pixel location of the current station
13490 int pixxc, pixyc;
13491 pixxc = r.x;
13492 pixyc = r.y;
13493
13494 // Adjust drawing size using logarithmic scale. tcvalue is
13495 // current in knots
13496 double a1 = fabs(tcvalue) * 10.;
13497 // Current values <= 0.1 knot will have no arrow
13498 a1 = wxMax(1.0, a1);
13499 double a2 = log10(a1);
13500
13501 float cscale = scale_factor * a2 * 0.3;
13502
13503 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13504 dc.SetPen(*porange_pen);
13505 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13506 cscale);
13507 // Draw text, if enabled
13508
13509 if (bDrawCurrentValues) {
13510 dc.SetFont(*pTCFont);
13511 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13512 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13513 }
13514 }
13515 } // scale
13516 }
13517 /* This is useful for debugging the TC database
13518 else
13519 {
13520 dc.SetPen ( *porange_pen );
13521 dc.SetBrush ( *pgray_brush );
13522 dc.DrawPolygon ( 4, d );
13523 }
13524 */
13525 }
13526 lon_last = lon;
13527 lat_last = lat;
13528 }
13529 }
13530 }
13531}
13532
13533void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13534 ShowSingleTideDialog(x, y, pvIDX);
13535}
13536
13537void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13538 if (!pvIDX) return; // Validate input
13539
13540 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13541
13542 // Check if a tide dialog is already open and visible
13543 if (pCwin && pCwin->IsShown()) {
13544 // Same tide station: bring existing dialog to front (preserves user
13545 // context)
13546 if (pCwin->GetCurrentIDX() == pNewIDX) {
13547 pCwin->Raise();
13548 pCwin->SetFocus();
13549
13550 // Provide subtle visual feedback that dialog is already open
13551 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13552 return;
13553 }
13554
13555 // Different tide station: close current dialog before opening new one
13556 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13557 }
13558
13559 if (pCwin) {
13560 // This shouldn't happen but ensures clean state
13561 pCwin->Destroy();
13562 pCwin = NULL;
13563 }
13564
13565 // Create and display new tide dialog
13566 pCwin = new TCWin(this, x, y, pvIDX);
13567
13568 // Ensure the dialog is properly shown and focused
13569 if (pCwin) {
13570 pCwin->Show();
13571 pCwin->Raise();
13572 pCwin->SetFocus();
13573 }
13574}
13575
13576bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13577
13579 if (pCwin) {
13580 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13581 }
13582}
13583
13584#define NUM_CURRENT_ARROW_POINTS 9
13585static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13586 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13587 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13588 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13589
13590void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13591 double scale) {
13592 if (scale > 1e-2) {
13593 float sin_rot = sin(rot_angle * PI / 180.);
13594 float cos_rot = cos(rot_angle * PI / 180.);
13595
13596 // Move to the first point
13597
13598 float xt = CurrentArrowArray[0].x;
13599 float yt = CurrentArrowArray[0].y;
13600
13601 float xp = (xt * cos_rot) - (yt * sin_rot);
13602 float yp = (xt * sin_rot) + (yt * cos_rot);
13603 int x1 = (int)(xp * scale);
13604 int y1 = (int)(yp * scale);
13605
13606 // Walk thru the point list
13607 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13608 xt = CurrentArrowArray[ip].x;
13609 yt = CurrentArrowArray[ip].y;
13610
13611 float xp = (xt * cos_rot) - (yt * sin_rot);
13612 float yp = (xt * sin_rot) + (yt * cos_rot);
13613 int x2 = (int)(xp * scale);
13614 int y2 = (int)(yp * scale);
13615
13616 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13617
13618 x1 = x2;
13619 y1 = y2;
13620 }
13621 }
13622}
13623
13624wxString ChartCanvas::FindValidUploadPort() {
13625 wxString port;
13626 // Try to use the saved persistent upload port first
13627 if (!g_uploadConnection.IsEmpty() &&
13628 g_uploadConnection.StartsWith("Serial")) {
13629 port = g_uploadConnection;
13630 }
13631
13632 else {
13633 // If there is no persistent upload port recorded (yet)
13634 // then use the first available serial connection which has output defined.
13635 for (auto *cp : TheConnectionParams()) {
13636 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13637 port << "Serial:" << cp->Port;
13638 }
13639 }
13640 return port;
13641}
13642
13643void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13644 if (!win) return;
13645
13646 if (NULL == g_pais_query_dialog_active) {
13647 int pos_x = g_ais_query_dialog_x;
13648 int pos_y = g_ais_query_dialog_y;
13649
13650 if (g_pais_query_dialog_active) {
13651 g_pais_query_dialog_active->Destroy();
13652 g_pais_query_dialog_active = new AISTargetQueryDialog();
13653 } else {
13654 g_pais_query_dialog_active = new AISTargetQueryDialog();
13655 }
13656
13657 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13658 wxPoint(pos_x, pos_y));
13659
13660 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13661 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13662 g_pais_query_dialog_active->SetMMSI(mmsi);
13663 g_pais_query_dialog_active->UpdateText();
13664 wxSize sz = g_pais_query_dialog_active->GetSize();
13665
13666 bool b_reset_pos = false;
13667#ifdef __WXMSW__
13668 // Support MultiMonitor setups which an allow negative window positions.
13669 // If the requested window title bar does not intersect any installed
13670 // monitor, then default to simple primary monitor positioning.
13671 RECT frame_title_rect;
13672 frame_title_rect.left = pos_x;
13673 frame_title_rect.top = pos_y;
13674 frame_title_rect.right = pos_x + sz.x;
13675 frame_title_rect.bottom = pos_y + 30;
13676
13677 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13678 b_reset_pos = true;
13679#else
13680
13681 // Make sure drag bar (title bar) of window intersects wxClient Area of
13682 // screen, with a little slop...
13683 wxRect window_title_rect; // conservative estimate
13684 window_title_rect.x = pos_x;
13685 window_title_rect.y = pos_y;
13686 window_title_rect.width = sz.x;
13687 window_title_rect.height = 30;
13688
13689 wxRect ClientRect = wxGetClientDisplayRect();
13690 ClientRect.Deflate(
13691 60, 60); // Prevent the new window from being too close to the edge
13692 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13693
13694#endif
13695
13696 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13697
13698 } else {
13699 g_pais_query_dialog_active->SetMMSI(mmsi);
13700 g_pais_query_dialog_active->UpdateText();
13701 }
13702
13703 g_pais_query_dialog_active->Show();
13704}
13705
13706void ChartCanvas::ToggleCanvasQuiltMode(void) {
13707 bool cur_mode = GetQuiltMode();
13708
13709 if (!GetQuiltMode())
13710 SetQuiltMode(true);
13711 else if (GetQuiltMode()) {
13712 SetQuiltMode(false);
13713 g_sticky_chart = GetQuiltReferenceChartIndex();
13714 }
13715
13716 if (cur_mode != GetQuiltMode()) {
13717 SetupCanvasQuiltMode();
13718 DoCanvasUpdate();
13719 InvalidateGL();
13720 Refresh();
13721 }
13722 // TODO What to do about this?
13723 // g_bQuiltEnable = GetQuiltMode();
13724
13725 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13726 if (ps52plib) ps52plib->GenerateStateHash();
13727
13728 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13729 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13730}
13731
13732void ChartCanvas::DoCanvasStackDelta(int direction) {
13733 if (!GetQuiltMode()) {
13734 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13735 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13736 if ((current_stack_index + direction) < 0) return;
13737
13738 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13739 int new_dbIndex =
13740 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13741
13742 if (IsChartQuiltableRef(new_dbIndex)) {
13743 ToggleCanvasQuiltMode();
13744 SelectQuiltRefdbChart(new_dbIndex);
13745 m_bpersistent_quilt = false;
13746 }
13747 } else {
13748 SelectChartFromStack(current_stack_index + direction);
13749 }
13750 } else {
13751 std::vector<int> piano_chart_index_array =
13752 GetQuiltExtendedStackdbIndexArray();
13753 int refdb = GetQuiltRefChartdbIndex();
13754
13755 // Find the ref chart in the stack
13756 int current_index = -1;
13757 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13758 if (refdb == piano_chart_index_array[i]) {
13759 current_index = i;
13760 break;
13761 }
13762 }
13763 if (current_index == -1) return;
13764
13765 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13766 int target_family = ctet.GetChartFamily();
13767
13768 int new_index = -1;
13769 int check_index = current_index + direction;
13770 bool found = false;
13771 int check_dbIndex = -1;
13772 int new_dbIndex = -1;
13773
13774 // When quilted. switch within the same chart family
13775 while (!found &&
13776 (unsigned int)check_index < piano_chart_index_array.size() &&
13777 (check_index >= 0)) {
13778 check_dbIndex = piano_chart_index_array[check_index];
13779 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13780 if (target_family == cte.GetChartFamily()) {
13781 found = true;
13782 new_index = check_index;
13783 new_dbIndex = check_dbIndex;
13784 break;
13785 }
13786
13787 check_index += direction;
13788 }
13789
13790 if (!found) return;
13791
13792 if (!IsChartQuiltableRef(new_dbIndex)) {
13793 ToggleCanvasQuiltMode();
13794 SelectdbChart(new_dbIndex);
13795 m_bpersistent_quilt = true;
13796 } else {
13797 SelectQuiltRefChart(new_index);
13798 }
13799 }
13800
13801 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13802 // (checkmarks etc)
13803 SetQuiltChartHiLiteIndex(-1);
13804
13805 ReloadVP();
13806}
13807
13808//--------------------------------------------------------------------------------------------------------
13809//
13810// Toolbar support
13811//
13812//--------------------------------------------------------------------------------------------------------
13813
13814void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13815 // Handle the per-canvas toolbar clicks here
13816
13817 switch (event.GetId()) {
13818 case ID_ZOOMIN: {
13819 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13820 break;
13821 }
13822
13823 case ID_ZOOMOUT: {
13824 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13825 break;
13826 }
13827
13828 case ID_STKUP:
13829 DoCanvasStackDelta(1);
13830 DoCanvasUpdate();
13831 break;
13832
13833 case ID_STKDN:
13834 DoCanvasStackDelta(-1);
13835 DoCanvasUpdate();
13836 break;
13837
13838 case ID_FOLLOW: {
13839 TogglebFollow();
13840 break;
13841 }
13842
13843 case ID_CURRENT: {
13844 ShowCurrents(!GetbShowCurrent());
13845 ReloadVP();
13846 Refresh(false);
13847 break;
13848 }
13849
13850 case ID_TIDE: {
13851 ShowTides(!GetbShowTide());
13852 ReloadVP();
13853 Refresh(false);
13854 break;
13855 }
13856
13857 case ID_ROUTE: {
13858 if (0 == m_routeState) {
13859 StartRoute();
13860 } else {
13861 FinishRoute();
13862 }
13863
13864#ifdef __ANDROID__
13865 androidSetRouteAnnunciator(m_routeState == 1);
13866#endif
13867 break;
13868 }
13869
13870 case ID_AIS: {
13871 SetAISCanvasDisplayStyle(-1);
13872 break;
13873 }
13874
13875 default:
13876 break;
13877 }
13878
13879 // And then let gFrame handle the rest....
13880 event.Skip();
13881}
13882
13883void ChartCanvas::SetShowAIS(bool show) {
13884 m_bShowAIS = show;
13885 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13886 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13887}
13888
13889void ChartCanvas::SetAttenAIS(bool show) {
13890 m_bShowAISScaled = show;
13891 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13892 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13893}
13894
13895void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
13896 // make some arrays to hold the dfferences between cycle steps
13897 // show all, scaled, hide all
13898 bool bShowAIS_Array[3] = {true, true, false};
13899 bool bShowScaled_Array[3] = {false, true, true};
13900 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
13901 _("Attenuate less critical AIS targets"),
13902 _("Hide AIS Targets")};
13903 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
13904 int ArraySize = 3;
13905 int AIS_Toolbar_Switch = 0;
13906 if (StyleIndx == -1) { // -1 means coming from toolbar button
13907 // find current state of switch
13908 for (int i = 1; i < ArraySize; i++) {
13909 if ((bShowAIS_Array[i] == m_bShowAIS) &&
13910 (bShowScaled_Array[i] == m_bShowAISScaled))
13911 AIS_Toolbar_Switch = i;
13912 }
13913 AIS_Toolbar_Switch++; // we did click so continu with next item
13914 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
13915 AIS_Toolbar_Switch++;
13916
13917 } else { // coming from menu bar.
13918 AIS_Toolbar_Switch = StyleIndx;
13919 }
13920 // make sure we are not above array
13921 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
13922
13923 int AIS_Toolbar_Switch_Next =
13924 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
13925 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
13926 AIS_Toolbar_Switch_Next++;
13927 if (AIS_Toolbar_Switch_Next >= ArraySize)
13928 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
13929
13930 // Set found values to global and member variables
13931 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
13932 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
13933}
13934
13935void ChartCanvas::TouchAISToolActive(void) {}
13936
13937void ChartCanvas::UpdateAISTBTool(void) {}
13938
13939//---------------------------------------------------------------------------------
13940//
13941// Compass/GPS status icon support
13942//
13943//---------------------------------------------------------------------------------
13944
13945void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
13946 // Look for change in overlap or positions
13947 bool b_update = false;
13948 int cc1_edge_comp = 2;
13949 wxRect rect = m_Compass->GetRect();
13950 wxSize parent_size = GetSize();
13951
13952 parent_size *= m_displayScale;
13953
13954 // check to see if it would overlap if it was in its home position (upper
13955 // right)
13956 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
13957 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
13958 wxRect compass_rect(compass_pt, rect.GetSize());
13959
13960 m_Compass->Move(compass_pt);
13961
13962 if (m_Compass && m_Compass->IsShown())
13963 m_Compass->UpdateStatus(b_force_new | b_update);
13964
13965 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
13966 scaler = wxMax(scaler, 1.0);
13967 wxPoint note_point = wxPoint(
13968 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
13969 m_notification_button->Move(note_point);
13970 m_notification_button->UpdateStatus();
13971
13972 if (b_force_new | b_update) Refresh();
13973}
13974
13975void ChartCanvas::SelectChartFromStack(int index, bool bDir,
13976 ChartTypeEnum New_Type,
13977 ChartFamilyEnum New_Family) {
13978 if (!GetpCurrentStack()) return;
13979 if (!ChartData) return;
13980
13981 if (index < GetpCurrentStack()->nEntry) {
13982 // Open the new chart
13983 ChartBase *pTentative_Chart;
13984 pTentative_Chart = ChartData->OpenStackChartConditional(
13985 GetpCurrentStack(), index, bDir, New_Type, New_Family);
13986
13987 if (pTentative_Chart) {
13988 if (m_singleChart) m_singleChart->Deactivate();
13989
13990 m_singleChart = pTentative_Chart;
13991 m_singleChart->Activate();
13992
13993 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13994 GetpCurrentStack(), m_singleChart->GetFullPath());
13995 }
13996
13997 // Setup the view
13998 double zLat, zLon;
13999 if (m_bFollow) {
14000 zLat = gLat;
14001 zLon = gLon;
14002 } else {
14003 zLat = m_vLat;
14004 zLon = m_vLon;
14005 }
14006
14007 double best_scale_ppm = GetBestVPScale(m_singleChart);
14008 double rotation = GetVPRotation();
14009 double oldskew = GetVPSkew();
14010 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14011
14012 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14013 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14014 if (fabs(newskew) > 0.0001) rotation = newskew;
14015 }
14016
14017 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14018
14019 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14020 }
14021
14022 // refresh Piano
14023 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14024 if (idx < 0) return;
14025
14026 std::vector<int> piano_active_chart_index_array;
14027 piano_active_chart_index_array.push_back(
14028 GetpCurrentStack()->GetCurrentEntrydbIndex());
14029 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14030}
14031
14032void ChartCanvas::SelectdbChart(int dbindex) {
14033 if (!GetpCurrentStack()) return;
14034 if (!ChartData) return;
14035
14036 if (dbindex >= 0) {
14037 // Open the new chart
14038 ChartBase *pTentative_Chart;
14039 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14040
14041 if (pTentative_Chart) {
14042 if (m_singleChart) m_singleChart->Deactivate();
14043
14044 m_singleChart = pTentative_Chart;
14045 m_singleChart->Activate();
14046
14047 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14048 GetpCurrentStack(), m_singleChart->GetFullPath());
14049 }
14050
14051 // Setup the view
14052 double zLat, zLon;
14053 if (m_bFollow) {
14054 zLat = gLat;
14055 zLon = gLon;
14056 } else {
14057 zLat = m_vLat;
14058 zLon = m_vLon;
14059 }
14060
14061 double best_scale_ppm = GetBestVPScale(m_singleChart);
14062
14063 if (m_singleChart)
14064 SetViewPoint(zLat, zLon, best_scale_ppm,
14065 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14066
14067 // SetChartUpdatePeriod( );
14068
14069 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14070 }
14071
14072 // TODO refresh_Piano();
14073}
14074
14075void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14076 double target_scale = GetVP().view_scale_ppm;
14077
14078 if (!GetQuiltMode()) {
14079 if (GetpCurrentStack()) {
14080 int stack_index = -1;
14081 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14082 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14083 if (check_dbIndex < 0) continue;
14084 const ChartTableEntry &cte =
14085 ChartData->GetChartTableEntry(check_dbIndex);
14086 if (type == cte.GetChartType()) {
14087 stack_index = i;
14088 break;
14089 } else if (family == cte.GetChartFamily()) {
14090 stack_index = i;
14091 break;
14092 }
14093 }
14094
14095 if (stack_index >= 0) {
14096 SelectChartFromStack(stack_index);
14097 }
14098 }
14099 } else {
14100 int sel_dbIndex = -1;
14101 std::vector<int> piano_chart_index_array =
14102 GetQuiltExtendedStackdbIndexArray();
14103 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14104 int check_dbIndex = piano_chart_index_array[i];
14105 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14106 if (type == cte.GetChartType()) {
14107 if (IsChartQuiltableRef(check_dbIndex)) {
14108 sel_dbIndex = check_dbIndex;
14109 break;
14110 }
14111 } else if (family == cte.GetChartFamily()) {
14112 if (IsChartQuiltableRef(check_dbIndex)) {
14113 sel_dbIndex = check_dbIndex;
14114 break;
14115 }
14116 }
14117 }
14118
14119 if (sel_dbIndex >= 0) {
14120 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14121 // Re-qualify the quilt reference chart selection
14122 AdjustQuiltRefChart();
14123 }
14124
14125 // Now reset the scale to the target...
14126 SetVPScale(target_scale);
14127 }
14128
14129 SetQuiltChartHiLiteIndex(-1);
14130
14131 ReloadVP();
14132}
14133
14134bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14135 return std::find(m_tile_yesshow_index_array.begin(),
14136 m_tile_yesshow_index_array.end(),
14137 index) != m_tile_yesshow_index_array.end();
14138}
14139
14140bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14141 return std::find(m_tile_noshow_index_array.begin(),
14142 m_tile_noshow_index_array.end(),
14143 index) != m_tile_noshow_index_array.end();
14144}
14145
14146void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14147 if (std::find(m_tile_noshow_index_array.begin(),
14148 m_tile_noshow_index_array.end(),
14149 index) == m_tile_noshow_index_array.end()) {
14150 m_tile_noshow_index_array.push_back(index);
14151 }
14152}
14153
14154//-------------------------------------------------------------------------------------------------------
14155//
14156// Piano support
14157//
14158//-------------------------------------------------------------------------------------------------------
14159
14160void ChartCanvas::HandlePianoClick(
14161 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14162 if (g_options && g_options->IsShown())
14163 return; // Piano might be invalid due to chartset updates.
14164 if (!m_pCurrentStack) return;
14165 if (!ChartData) return;
14166
14167 // stop movement or on slow computer we may get something like :
14168 // zoom out with the wheel (timer is set)
14169 // quickly click and display a chart, which may zoom in
14170 // but the delayed timer fires first and it zooms out again!
14171 StopMovement();
14172
14173 // When switching by piano key click, we may appoint the new target chart to
14174 // be any chart in the composite array.
14175 // As an improvement to UX, find the chart that is "closest" to the current
14176 // vp,
14177 // and select that chart. This will cause a jump to the centroid of that
14178 // chart
14179
14180 double distance = 25000; // RTW
14181 int closest_index = -1;
14182 for (int chart_index : selected_dbIndex_array) {
14183 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14184 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14185 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14186
14187 // measure distance as Manhattan style
14188 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14189 if (test_distance < distance) {
14190 distance = test_distance;
14191 closest_index = chart_index;
14192 }
14193 }
14194
14195 int selected_dbIndex = selected_dbIndex_array[0];
14196 if (closest_index >= 0) selected_dbIndex = closest_index;
14197
14198 if (!GetQuiltMode()) {
14199 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14200 if (IsChartQuiltableRef(selected_dbIndex)) {
14201 ToggleCanvasQuiltMode();
14202 SelectQuiltRefdbChart(selected_dbIndex);
14203 m_bpersistent_quilt = false;
14204 } else {
14205 SelectChartFromStack(selected_index);
14206 }
14207 } else {
14208 SelectChartFromStack(selected_index);
14209 g_sticky_chart = selected_dbIndex;
14210 }
14211
14212 if (m_singleChart)
14213 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14214 } else {
14215 // Handle MBTiles overlays first
14216 // Left click simply toggles the noshow array index entry
14217 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14218 bool bfound = false;
14219 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14220 if (m_tile_noshow_index_array[i] ==
14221 selected_dbIndex) { // chart is in the noshow list
14222 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14223 i); // erase it
14224 bfound = true;
14225 break;
14226 }
14227 }
14228 if (!bfound) {
14229 m_tile_noshow_index_array.push_back(selected_dbIndex);
14230 }
14231
14232 // If not already present, add this tileset to the "yes_show" array.
14233 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14234 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14235 }
14236
14237 else {
14238 if (IsChartQuiltableRef(selected_dbIndex)) {
14239 // if( ChartData ) ChartData->PurgeCache();
14240
14241 // If the chart is a vector chart, and of very large scale,
14242 // then we had better set the new scale directly to avoid excessive
14243 // underzoom on, eg, Inland ENCs
14244 bool set_scale = false;
14245 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14246 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14247 set_scale = true;
14248 }
14249 }
14250
14251 if (!set_scale) {
14252 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14253 } else {
14254 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14255
14256 // Adjust scale so that the selected chart is underzoomed/overzoomed
14257 // by a controlled amount
14258 ChartBase *pc =
14259 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14260 if (pc) {
14261 double proposed_scale_onscreen =
14263
14264 if (g_bPreserveScaleOnX) {
14265 proposed_scale_onscreen =
14266 wxMin(proposed_scale_onscreen,
14267 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14268 GetCanvasWidth()));
14269 } else {
14270 proposed_scale_onscreen =
14271 wxMin(proposed_scale_onscreen,
14272 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14273 GetCanvasWidth()));
14274
14275 proposed_scale_onscreen =
14276 wxMax(proposed_scale_onscreen,
14277 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14279 }
14280
14281 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14282 }
14283 }
14284 } else {
14285 ToggleCanvasQuiltMode();
14286 SelectdbChart(selected_dbIndex);
14287 m_bpersistent_quilt = true;
14288 }
14289 }
14290 }
14291
14292 SetQuiltChartHiLiteIndex(-1);
14293 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14294 // (checkmarks etc)
14295 HideChartInfoWindow();
14296 DoCanvasUpdate();
14297 ReloadVP(); // Pick up the new selections
14298}
14299
14300void ChartCanvas::HandlePianoRClick(
14301 int x, int y, int selected_index,
14302 const std::vector<int> &selected_dbIndex_array) {
14303 if (g_options && g_options->IsShown())
14304 return; // Piano might be invalid due to chartset updates.
14305 if (!GetpCurrentStack()) return;
14306
14307 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14308 UpdateCanvasControlBar();
14309
14310 SetQuiltChartHiLiteIndex(-1);
14311}
14312
14313void ChartCanvas::HandlePianoRollover(
14314 int selected_index, const std::vector<int> &selected_dbIndex_array,
14315 int n_charts, int scale) {
14316 if (g_options && g_options->IsShown())
14317 return; // Piano might be invalid due to chartset updates.
14318 if (!GetpCurrentStack()) return;
14319 if (!ChartData) return;
14320
14321 if (ChartData->IsBusy()) return;
14322
14323 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14324
14325 if (!GetQuiltMode()) {
14326 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14327 } else {
14328 // Select the correct vector
14329 std::vector<int> piano_chart_index_array;
14330 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14331 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14332 if ((GetpCurrentStack()->nEntry > 1) ||
14333 (piano_chart_index_array.size() >= 1)) {
14334 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14335
14336 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14337 ReloadVP(false); // no VP adjustment allowed
14338 } else if (GetpCurrentStack()->nEntry == 1) {
14339 const ChartTableEntry &cte =
14340 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14341 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14342 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14343 ReloadVP(false);
14344 } else if ((-1 == selected_index) &&
14345 (0 == selected_dbIndex_array.size())) {
14346 ShowChartInfoWindow(key_location.x, -1);
14347 }
14348 }
14349 } else {
14350 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14351
14352 if ((GetpCurrentStack()->nEntry > 1) ||
14353 (piano_chart_index_array.size() >= 1)) {
14354 if (n_charts > 1)
14355 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14356 selected_dbIndex_array);
14357 else if (n_charts == 1)
14358 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14359
14360 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14361 ReloadVP(false); // no VP adjustment allowed
14362 }
14363 }
14364 }
14365}
14366
14367void ChartCanvas::ClearPianoRollover() {
14368 ClearQuiltChartHiLiteIndexArray();
14369 ShowChartInfoWindow(0, -1);
14370 std::vector<int> vec;
14371 ShowCompositeInfoWindow(0, 0, 0, vec);
14372 ReloadVP(false);
14373}
14374
14375void ChartCanvas::UpdateCanvasControlBar(void) {
14376 if (m_pianoFrozen) return;
14377
14378 if (!GetpCurrentStack()) return;
14379 if (!ChartData) return;
14380 if (!g_bShowChartBar) return;
14381
14382 int sel_type = -1;
14383 int sel_family = -1;
14384
14385 std::vector<int> piano_chart_index_array;
14386 std::vector<int> empty_piano_chart_index_array;
14387
14388 wxString old_hash = m_Piano->GetStoredHash();
14389
14390 if (GetQuiltMode()) {
14391 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14392 GetQuiltFullScreendbIndexArray());
14393
14394 std::vector<int> piano_active_chart_index_array =
14395 GetQuiltCandidatedbIndexArray();
14396 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14397
14398 std::vector<int> piano_eclipsed_chart_index_array =
14399 GetQuiltEclipsedStackdbIndexArray();
14400 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14401
14402 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14403 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14404
14405 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14406 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14407 } else {
14408 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14409 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14410 // TODO refresh_Piano();
14411
14412 if (m_singleChart) {
14413 sel_type = m_singleChart->GetChartType();
14414 sel_family = m_singleChart->GetChartFamily();
14415 }
14416 }
14417
14418 // Set up the TMerc and Skew arrays
14419 std::vector<int> piano_skew_chart_index_array;
14420 std::vector<int> piano_tmerc_chart_index_array;
14421 std::vector<int> piano_poly_chart_index_array;
14422
14423 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14424 const ChartTableEntry &ctei =
14425 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14426 double skew_norm = ctei.GetChartSkew();
14427 if (skew_norm > 180.) skew_norm -= 360.;
14428
14429 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14430 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14431
14432 // Polyconic skewed charts should show as skewed
14433 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14434 if (fabs(skew_norm) > 1.)
14435 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14436 else
14437 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14438 } else if (fabs(skew_norm) > 1.)
14439 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14440 }
14441 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14442 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14443 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14444
14445 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14446 if (new_hash != old_hash) {
14447 m_Piano->FormatKeys();
14448 HideChartInfoWindow();
14449 m_Piano->ResetRollover();
14450 SetQuiltChartHiLiteIndex(-1);
14451 m_brepaint_piano = true;
14452 }
14453
14454 // Create a bitmask int that describes what Family/Type of charts are shown in
14455 // the bar, and notify the platform.
14456 int mask = 0;
14457 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14458 const ChartTableEntry &ctei =
14459 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14460 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14461 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14462 if (e == CHART_FAMILY_RASTER) mask |= 1;
14463 if (e == CHART_FAMILY_VECTOR) {
14464 if (t == CHART_TYPE_CM93COMP)
14465 mask |= 4;
14466 else
14467 mask |= 2;
14468 }
14469 }
14470
14471 wxString s_indicated;
14472 if (sel_type == CHART_TYPE_CM93COMP)
14473 s_indicated = "cm93";
14474 else {
14475 if (sel_family == CHART_FAMILY_RASTER)
14476 s_indicated = "raster";
14477 else if (sel_family == CHART_FAMILY_VECTOR)
14478 s_indicated = "vector";
14479 }
14480
14481 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14482}
14483
14484void ChartCanvas::FormatPianoKeys(void) { m_Piano->FormatKeys(); }
14485
14486void ChartCanvas::PianoPopupMenu(
14487 int x, int y, int selected_index,
14488 const std::vector<int> &selected_dbIndex_array) {
14489 if (!GetpCurrentStack()) return;
14490
14491 // No context menu if quilting is disabled
14492 if (!GetQuiltMode()) return;
14493
14494 m_piano_ctx_menu = new wxMenu();
14495
14496 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14497 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14498 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14499 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14500 } else {
14501 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14502 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14503 // wxEVT_COMMAND_MENU_SELECTED,
14504 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14505
14506 menu_selected_dbIndex = selected_dbIndex_array[0];
14507 menu_selected_index = selected_index;
14508
14509 // Search the no-show array
14510 bool b_is_in_noshow = false;
14511 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14512 if (m_quilt_noshow_index_array[i] ==
14513 menu_selected_dbIndex) // chart is in the noshow list
14514 {
14515 b_is_in_noshow = true;
14516 break;
14517 }
14518 }
14519
14520 if (b_is_in_noshow) {
14521 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14522 _("Show This Chart"));
14523 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14524 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14525 } else if (GetpCurrentStack()->nEntry > 1) {
14526 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14527 _("Hide This Chart"));
14528 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14529 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14530 }
14531 }
14532
14533 wxPoint pos = wxPoint(x, y - 30);
14534
14535 // Invoke the drop-down menu
14536 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14537 PopupMenu(m_piano_ctx_menu, pos);
14538
14539 delete m_piano_ctx_menu;
14540 m_piano_ctx_menu = NULL;
14541
14542 HideChartInfoWindow();
14543 m_Piano->ResetRollover();
14544
14545 SetQuiltChartHiLiteIndex(-1);
14546 ClearQuiltChartHiLiteIndexArray();
14547
14548 ReloadVP();
14549}
14550
14551void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14552 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14553 if (m_quilt_noshow_index_array[i] ==
14554 menu_selected_dbIndex) // chart is in the noshow list
14555 {
14556 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14557 break;
14558 }
14559 }
14560}
14561
14562void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14563 if (!GetpCurrentStack()) return;
14564 if (!ChartData) return;
14565
14566 RemoveChartFromQuilt(menu_selected_dbIndex);
14567
14568 // It could happen that the chart being disabled is the reference
14569 // chart....
14570 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14571 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14572
14573 int i = menu_selected_index + 1; // select next smaller scale chart
14574 bool b_success = false;
14575 while (i < GetpCurrentStack()->nEntry - 1) {
14576 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14577 if (type == ChartData->GetDBChartType(dbIndex)) {
14578 SelectQuiltRefChart(i);
14579 b_success = true;
14580 break;
14581 }
14582 i++;
14583 }
14584
14585 // If that did not work, try to select the next larger scale compatible
14586 // chart
14587 if (!b_success) {
14588 i = menu_selected_index - 1;
14589 while (i > 0) {
14590 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14591 if (type == ChartData->GetDBChartType(dbIndex)) {
14592 SelectQuiltRefChart(i);
14593 b_success = true;
14594 break;
14595 }
14596 i--;
14597 }
14598 }
14599 }
14600}
14601
14602void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14603 // Remove the item from the list (if it appears) to avoid multiple addition
14604 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14605 if (m_quilt_noshow_index_array[i] ==
14606 dbIndex) // chart is already in the noshow list
14607 {
14608 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14609 break;
14610 }
14611 }
14612
14613 m_quilt_noshow_index_array.push_back(dbIndex);
14614}
14615
14616bool ChartCanvas::UpdateS52State() {
14617 bool retval = false;
14618 // printf(" update %d\n", IsPrimaryCanvas());
14619
14620 if (ps52plib) {
14621 ps52plib->SetShowS57Text(m_encShowText);
14622 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14623 ps52plib->m_bShowSoundg = m_encShowDepth;
14624 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14625 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14626
14627 // Lights
14628 if (!m_encShowLights) // On, going off
14629 ps52plib->AddObjNoshow("LIGHTS");
14630 else // Off, going on
14631 ps52plib->RemoveObjNoshow("LIGHTS");
14632 ps52plib->SetLightsOff(!m_encShowLights);
14633 ps52plib->m_bExtendLightSectors = true;
14634
14635 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14636 ps52plib->SetAnchorOn(m_encShowAnchor);
14637 ps52plib->SetQualityOfData(m_encShowDataQual);
14638 }
14639
14640 return retval;
14641}
14642
14643void ChartCanvas::SetShowENCDataQual(bool show) {
14644 m_encShowDataQual = show;
14645 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14646 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14647
14648 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14649}
14650
14651void ChartCanvas::SetShowENCText(bool show) {
14652 m_encShowText = show;
14653 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14654 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14655
14656 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14657}
14658
14659void ChartCanvas::SetENCDisplayCategory(int category) {
14660 m_encDisplayCategory = category;
14661 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14662}
14663
14664void ChartCanvas::SetShowENCDepth(bool show) {
14665 m_encShowDepth = show;
14666 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14667 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14668
14669 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14670}
14671
14672void ChartCanvas::SetShowENCLightDesc(bool show) {
14673 m_encShowLightDesc = show;
14674 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14675 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14676
14677 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14678}
14679
14680void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14681 m_encShowBuoyLabels = show;
14682 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14683}
14684
14685void ChartCanvas::SetShowENCLights(bool show) {
14686 m_encShowLights = show;
14687 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14688 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14689
14690 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14691}
14692
14693void ChartCanvas::SetShowENCAnchor(bool show) {
14694 m_encShowAnchor = show;
14695 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14696 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14697
14698 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14699}
14700
14701wxRect ChartCanvas::GetMUIBarRect() {
14702 wxRect rv;
14703 if (m_muiBar) {
14704 rv = m_muiBar->GetRect();
14705 }
14706
14707 return rv;
14708}
14709
14710void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14711 if (!GetAlertString().IsEmpty()) {
14712 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14713 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14714
14715 dc.SetFont(*pfont);
14716 dc.SetPen(*wxTRANSPARENT_PEN);
14717
14718 dc.SetBrush(wxColour(243, 229, 47));
14719 int w, h;
14720 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14721 h += 2;
14722 // int yp = vp.pix_height - 20 - h;
14723
14724 wxRect sbr = GetScaleBarRect();
14725 int xp = sbr.x + sbr.width + 10;
14726 int yp = (sbr.y + sbr.height) - h;
14727
14728 int wdraw = w + 10;
14729 dc.DrawRectangle(xp, yp, wdraw, h);
14730 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14731 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14732 }
14733}
14734
14735//--------------------------------------------------------------------------------------------------------
14736// Screen Brightness Control Support Routines
14737//
14738//--------------------------------------------------------------------------------------------------------
14739
14740#ifdef __UNIX__
14741#define BRIGHT_XCALIB
14742#define __OPCPN_USEICC__
14743#endif
14744
14745#ifdef __OPCPN_USEICC__
14746int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14747 double co_green, double co_blue);
14748
14749wxString temp_file_name;
14750#endif
14751
14752#if 0
14753class ocpnCurtain: public wxDialog
14754{
14755 DECLARE_CLASS( ocpnCurtain )
14756 DECLARE_EVENT_TABLE()
14757
14758public:
14759 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14760 ~ocpnCurtain( );
14761 bool ProcessEvent(wxEvent& event);
14762
14763};
14764
14765IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14766
14767BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14768END_EVENT_TABLE()
14769
14770ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14771{
14772 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14773}
14774
14775ocpnCurtain::~ocpnCurtain()
14776{
14777}
14778
14779bool ocpnCurtain::ProcessEvent(wxEvent& event)
14780{
14781 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14782 return GetParent()->GetEventHandler()->ProcessEvent(event);
14783}
14784#endif
14785
14786#ifdef _WIN32
14787#include <windows.h>
14788
14789HMODULE hGDI32DLL;
14790typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14791typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14792SetDeviceGammaRamp_ptr_type
14793 g_pSetDeviceGammaRamp; // the API entry points in the dll
14794GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14795
14796WORD *g_pSavedGammaMap;
14797
14798#endif
14799
14800int InitScreenBrightness(void) {
14801#ifdef _WIN32
14802#ifdef ocpnUSE_GL
14803 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14804 HDC hDC;
14805 BOOL bbr;
14806
14807 if (NULL == hGDI32DLL) {
14808 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14809
14810 if (NULL != hGDI32DLL) {
14811 // Get the entry points of the required functions
14812 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14813 hGDI32DLL, "SetDeviceGammaRamp");
14814 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14815 hGDI32DLL, "GetDeviceGammaRamp");
14816
14817 // If the functions are not found, unload the DLL and return false
14818 if ((NULL == g_pSetDeviceGammaRamp) ||
14819 (NULL == g_pGetDeviceGammaRamp)) {
14820 FreeLibrary(hGDI32DLL);
14821 hGDI32DLL = NULL;
14822 return 0;
14823 }
14824 }
14825 }
14826
14827 // Interface is ready, so....
14828 // Get some storage
14829 if (!g_pSavedGammaMap) {
14830 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14831
14832 hDC = GetDC(NULL); // Get the full screen DC
14833 bbr = g_pGetDeviceGammaRamp(
14834 hDC, g_pSavedGammaMap); // Get the existing ramp table
14835 ReleaseDC(NULL, hDC); // Release the DC
14836 }
14837
14838 // On Windows hosts, try to adjust the registry to allow full range
14839 // setting of Gamma table This is an undocumented Windows hack.....
14840 wxRegKey *pRegKey = new wxRegKey(
14841 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
14842 "NT\\CurrentVersion\\ICM");
14843 if (!pRegKey->Exists()) pRegKey->Create();
14844 pRegKey->SetValue("GdiIcmGammaRange", 256);
14845
14846 g_brightness_init = true;
14847 return 1;
14848 }
14849#endif
14850
14851 {
14852 if (NULL == g_pcurtain) {
14853 if (gFrame->CanSetTransparent()) {
14854 // Build the curtain window
14855 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, "",
14856 wxPoint(0, 0), ::wxGetDisplaySize(),
14857 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14858 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14859
14860 // g_pcurtain = new ocpnCurtain(gFrame,
14861 // wxPoint(0,0),::wxGetDisplaySize(),
14862 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14863 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14864
14865 g_pcurtain->Hide();
14866
14867 HWND hWnd = GetHwndOf(g_pcurtain);
14868 SetWindowLong(hWnd, GWL_EXSTYLE,
14869 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14870 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14871 g_pcurtain->SetTransparent(0);
14872
14873 g_pcurtain->Maximize();
14874 g_pcurtain->Show();
14875
14876 // All of this is obtuse, but necessary for Windows...
14877 g_pcurtain->Enable();
14878 g_pcurtain->Disable();
14879
14880 gFrame->Disable();
14881 gFrame->Enable();
14882 // SetFocus();
14883 }
14884 }
14885 g_brightness_init = true;
14886
14887 return 1;
14888 }
14889#else
14890 // Look for "xcalib" application
14891 wxString cmd("xcalib -version");
14892
14893 wxArrayString output;
14894 long r = wxExecute(cmd, output);
14895 if (0 != r)
14896 wxLogMessage(
14897 " External application \"xcalib\" not found. Screen brightness "
14898 "not changed.");
14899
14900 g_brightness_init = true;
14901 return 0;
14902#endif
14903}
14904
14905int RestoreScreenBrightness(void) {
14906#ifdef _WIN32
14907
14908 if (g_pSavedGammaMap) {
14909 HDC hDC = GetDC(NULL); // Get the full screen DC
14910 g_pSetDeviceGammaRamp(hDC,
14911 g_pSavedGammaMap); // Restore the saved ramp table
14912 ReleaseDC(NULL, hDC); // Release the DC
14913
14914 free(g_pSavedGammaMap);
14915 g_pSavedGammaMap = NULL;
14916 }
14917
14918 if (g_pcurtain) {
14919 g_pcurtain->Close();
14920 g_pcurtain->Destroy();
14921 g_pcurtain = NULL;
14922 }
14923
14924 g_brightness_init = false;
14925 return 1;
14926
14927#endif
14928
14929#ifdef BRIGHT_XCALIB
14930 if (g_brightness_init) {
14931 wxString cmd;
14932 cmd = "xcalib -clear";
14933 wxExecute(cmd, wxEXEC_ASYNC);
14934 g_brightness_init = false;
14935 }
14936
14937 return 1;
14938#endif
14939
14940 return 0;
14941}
14942
14943// Set brightness. [0..100]
14944int SetScreenBrightness(int brightness) {
14945#ifdef _WIN32
14946
14947 // Under Windows, we use the SetDeviceGammaRamp function which exists in
14948 // some (most modern?) versions of gdi32.dll Load the required library dll,
14949 // if not already in place
14950#ifdef ocpnUSE_GL
14951 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14952 if (g_pcurtain) {
14953 g_pcurtain->Close();
14954 g_pcurtain->Destroy();
14955 g_pcurtain = NULL;
14956 }
14957
14958 InitScreenBrightness();
14959
14960 if (NULL == hGDI32DLL) {
14961 // Unicode stuff.....
14962 wchar_t wdll_name[80];
14963 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
14964 LPCWSTR cstr = wdll_name;
14965
14966 hGDI32DLL = LoadLibrary(cstr);
14967
14968 if (NULL != hGDI32DLL) {
14969 // Get the entry points of the required functions
14970 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14971 hGDI32DLL, "SetDeviceGammaRamp");
14972 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14973 hGDI32DLL, "GetDeviceGammaRamp");
14974
14975 // If the functions are not found, unload the DLL and return false
14976 if ((NULL == g_pSetDeviceGammaRamp) ||
14977 (NULL == g_pGetDeviceGammaRamp)) {
14978 FreeLibrary(hGDI32DLL);
14979 hGDI32DLL = NULL;
14980 return 0;
14981 }
14982 }
14983 }
14984
14985 HDC hDC = GetDC(NULL); // Get the full screen DC
14986
14987 /*
14988 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
14989 if (cmcap != CM_GAMMA_RAMP)
14990 {
14991 wxLogMessage(" Video hardware does not support brightness control by
14992 gamma ramp adjustment."); return false;
14993 }
14994 */
14995
14996 int increment = brightness * 256 / 100;
14997
14998 // Build the Gamma Ramp table
14999 WORD GammaTable[3][256];
15000
15001 int table_val = 0;
15002 for (int i = 0; i < 256; i++) {
15003 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15004 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15005 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15006
15007 table_val += increment;
15008
15009 if (table_val > 65535) table_val = 65535;
15010 }
15011
15012 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15013 ReleaseDC(NULL, hDC); // Release the DC
15014
15015 return 1;
15016 }
15017#endif
15018
15019 {
15020 if (g_pSavedGammaMap) {
15021 HDC hDC = GetDC(NULL); // Get the full screen DC
15022 g_pSetDeviceGammaRamp(hDC,
15023 g_pSavedGammaMap); // Restore the saved ramp table
15024 ReleaseDC(NULL, hDC); // Release the DC
15025 }
15026
15027 if (brightness < 100) {
15028 if (NULL == g_pcurtain) InitScreenBrightness();
15029
15030 if (g_pcurtain) {
15031 int sbrite = wxMax(1, brightness);
15032 sbrite = wxMin(100, sbrite);
15033
15034 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15035 }
15036 } else {
15037 if (g_pcurtain) {
15038 g_pcurtain->Close();
15039 g_pcurtain->Destroy();
15040 g_pcurtain = NULL;
15041 }
15042 }
15043
15044 return 1;
15045 }
15046
15047#endif
15048
15049#ifdef BRIGHT_XCALIB
15050
15051 if (!g_brightness_init) {
15052 last_brightness = 100;
15053 g_brightness_init = true;
15054 temp_file_name = wxFileName::CreateTempFileName("");
15055 InitScreenBrightness();
15056 }
15057
15058#ifdef __OPCPN_USEICC__
15059 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15060 // desired, and then activate this temporary profile using xcalib <filename>
15061 if (!CreateSimpleICCProfileFile(
15062 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15063 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15064 wxString cmd("xcalib ");
15065 cmd += temp_file_name;
15066
15067 wxExecute(cmd, wxEXEC_ASYNC);
15068 }
15069
15070#else
15071 // Or, use "xcalib -co" to set overall contrast value
15072 // This is not as nice, since the -co parameter wants to be a fraction of
15073 // the current contrast, and values greater than 100 are not allowed. As a
15074 // result, increases of contrast must do a "-clear" step first, which
15075 // produces objectionable flashing.
15076 if (brightness > last_brightness) {
15077 wxString cmd;
15078 cmd = "xcalib -clear";
15079 wxExecute(cmd, wxEXEC_ASYNC);
15080
15081 ::wxMilliSleep(10);
15082
15083 int brite_adj = wxMax(1, brightness);
15084 cmd.Printf("xcalib -co %2d -a", brite_adj);
15085 wxExecute(cmd, wxEXEC_ASYNC);
15086 } else {
15087 int brite_adj = wxMax(1, brightness);
15088 int factor = (brite_adj * 100) / last_brightness;
15089 factor = wxMax(1, factor);
15090 wxString cmd;
15091 cmd.Printf("xcalib -co %2d -a", factor);
15092 wxExecute(cmd, wxEXEC_ASYNC);
15093 }
15094
15095#endif
15096
15097 last_brightness = brightness;
15098
15099#endif
15100
15101 return 0;
15102}
15103
15104#ifdef __OPCPN_USEICC__
15105
15106#define MLUT_TAG 0x6d4c5554L
15107#define VCGT_TAG 0x76636774L
15108
15109int GetIntEndian(unsigned char *s) {
15110 int ret;
15111 unsigned char *p;
15112 int i;
15113
15114 p = (unsigned char *)&ret;
15115
15116 if (1)
15117 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15118 else
15119 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15120
15121 return ret;
15122}
15123
15124unsigned short GetShortEndian(unsigned char *s) {
15125 unsigned short ret;
15126 unsigned char *p;
15127 int i;
15128
15129 p = (unsigned char *)&ret;
15130
15131 if (1)
15132 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15133 else
15134 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15135
15136 return ret;
15137}
15138
15139// Create a very simple Gamma correction file readable by xcalib
15140int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15141 double co_green, double co_blue) {
15142 FILE *fp;
15143
15144 if (file_name) {
15145 fp = fopen(file_name, "wb");
15146 if (!fp) return -1; /* file can not be created */
15147 } else
15148 return -1; /* filename char pointer not valid */
15149
15150 // Write header
15151 char header[128];
15152 for (int i = 0; i < 128; i++) header[i] = 0;
15153
15154 fwrite(header, 128, 1, fp);
15155
15156 // Num tags
15157 int numTags0 = 1;
15158 int numTags = GetIntEndian((unsigned char *)&numTags0);
15159 fwrite(&numTags, 1, 4, fp);
15160
15161 int tagName0 = VCGT_TAG;
15162 int tagName = GetIntEndian((unsigned char *)&tagName0);
15163 fwrite(&tagName, 1, 4, fp);
15164
15165 int tagOffset0 = 128 + 4 * sizeof(int);
15166 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15167 fwrite(&tagOffset, 1, 4, fp);
15168
15169 int tagSize0 = 1;
15170 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15171 fwrite(&tagSize, 1, 4, fp);
15172
15173 fwrite(&tagName, 1, 4, fp); // another copy of tag
15174
15175 fwrite(&tagName, 1, 4, fp); // dummy
15176
15177 // Table type
15178
15179 /* VideoCardGammaTable (The simplest type) */
15180 int gammatype0 = 0;
15181 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15182 fwrite(&gammatype, 1, 4, fp);
15183
15184 int numChannels0 = 3;
15185 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15186 fwrite(&numChannels, 1, 2, fp);
15187
15188 int numEntries0 = 256;
15189 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15190 fwrite(&numEntries, 1, 2, fp);
15191
15192 int entrySize0 = 1;
15193 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15194 fwrite(&entrySize, 1, 2, fp);
15195
15196 unsigned char ramp[256];
15197
15198 // Red ramp
15199 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15200 fwrite(ramp, 256, 1, fp);
15201
15202 // Green ramp
15203 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15204 fwrite(ramp, 256, 1, fp);
15205
15206 // Blue ramp
15207 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15208 fwrite(ramp, 256, 1, fp);
15209
15210 fclose(fp);
15211
15212 return 0;
15213}
15214#endif // __OPCPN_USEICC__
Class AisDecoder and helpers.
Global state for AIS decoder.
Class AISTargetAlertDialog and helpers.
Class AISTargetQueryDialog.
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:72
Charts database management
ChartGroupArray * g_pGroupArray
Global instance.
Definition chartdbs.cpp:54
BSB chart management.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1192
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1191
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1192
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1191
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:127
Base class for all chart types.
Definition chartbase.h:125
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:151
void CloseTideDialog()
Close any open tide dialog.
Definition chcanv.cpp:13578
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:4389
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4385
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3484
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13537
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:4335
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:770
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:477
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:508
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2220
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:7894
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7704
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:4913
float GetVPScale()
Return the ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:466
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:861
double GetCanvasTrueScale()
Return the physical pixels per meter at chart center, accounting for latitude distortion.
Definition chcanv.h:482
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4466
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5194
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:754
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4410
bool IsTideDialogOpen() const
Definition chcanv.cpp:13576
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:4472
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13533
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4330
bool SetViewPoint(double lat, double lon)
Set the viewport center point.
Definition chcanv.cpp:5213
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10048
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:467
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:388
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:449
wxColour GetFontColor(const wxString &TextElement) const
Gets the text color for a UI element.
Definition font_mgr.cpp:117
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Get a font object for a UI element.
Definition font_mgr.cpp:200
Modal dialog displaying hotkeys.
Definition hotkeys_dlg.h:29
Represents an index entry for tidal and current data.
Definition IDX_entry.h:49
char IDX_type
Entry type identifier "TCtcIUu".
Definition IDX_entry.h:61
double IDX_lat
Latitude of the station (in degrees, +North)
Definition IDX_entry.h:65
double IDX_lon
Longitude of the station (in degrees, +East)
Definition IDX_entry.h:64
bool b_skipTooDeep
Flag to skip processing if depth exceeds limit.
Definition IDX_entry.h:110
Station_Data * pref_sta_data
Pointer to the reference station data.
Definition IDX_entry.h:97
int IDX_rec_num
Record number for multiple entries with same name.
Definition IDX_entry.h:60
Definition kml.h:54
Modern User Interface Control Bar for OpenCPN.
Definition MUIBar.h:63
Dialog for displaying and editing waypoint properties.
Definition MarkInfo.h:215
Main application frame.
Definition ocpn_frame.h:138
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.
Definition OCPNRegion.h:156
A wrapper class for wxRegion with additional functionality.
Definition OCPNRegion.h:56
Definition piano.h:65
PluginLoader is a backend module without any direct GUI functionality.
A popup frame containing a detail slider for chart display.
Definition Quilt.h:83
bool Compose(const ViewPort &vp)
Definition Quilt.cpp:1717
Represents a waypoint or mark within the navigation system.
Definition route_point.h:70
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:98
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:202
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:335
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:236
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:207
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:287
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:246
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:272
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:277
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:297
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:223
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:314
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:178
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:403
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:818
Dialog for displaying query results of S57 objects.
Definition TCWin.h:45
IDX_entry * GetCurrentIDX() const
Definition TCWin.h:73
Represents a single point in a track.
Definition track.h:56
wxDateTime GetCreateTime(void)
Retrieves the creation timestamp of a track point as a wxDateTime object.
Definition track.cpp:143
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:149
bool UpdateProperties()
Represents a track, which is a series of connected track points.
Definition track.h:114
Definition undo.h:60
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:84
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:232
double ref_scale
The nominal scale of the "reference chart" for this view.
Definition viewport.h:249
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:261
void SetBoxes(void)
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp:825
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:242
void SetPixelScale(double scale)
Set the physical to logical pixel ratio for the display.
Definition viewport.cpp:135
int pix_width
Width of the viewport in physical pixels.
Definition viewport.h:259
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:147
double tilt
Tilt angle for perspective view in radians.
Definition viewport.h:244
double skew
Angular distortion (shear transform) applied to the viewport in radians.
Definition viewport.h:240
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:108
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:227
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:225
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:138
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:247
bool AddRoutePoint(RoutePoint *prp)
Add a point to list which owns it.
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:193
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:476
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:122
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)
double g_display_size_mm
Physical display width (mm)
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.
Hooks into gui available in model.
size_t g_current_monitor
Current monitor displaying main application frame.
Definition gui_vars.cpp:58
double g_current_monitor_dip_px_ratio
ratio to convert between DIP and physical pixels.
Definition gui_vars.cpp:56
bool g_b_overzoom_x
Allow high overzoom.
Definition gui_vars.cpp:41
Miscellaneous globals primarely used by gui layer.
Multiplexer class and helpers.
Class NavObj_dB.
Class NotificationManager.
#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 GetChartbarHeight(void)
Gets height of chart bar in pixels.
int GetCanvasCount()
Gets total number of chart canvases.
PI_DisCat GetENCDisplayCategory(int CanvasIndex)
Gets current ENC display category.
int GetCanvasIndexUnderMouse(void)
Gets index of chart canvas under mouse cursor.
double OCPN_GetWinDIPScaleFactor()
Gets Windows-specific DPI scaling factor.
Tools to send data to plugins.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:182
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.