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 "DetailSlider.h"
75#include "displays.h"
76#include "hotkeys_dlg.h"
77#include "FontMgr.h"
78#include "glTextureDescriptor.h"
79#include "GoToPositionDialog.h"
80#include "gshhs.h"
81#include "iENCToolbar.h"
82#include "kml.h"
83#include "line_clip.h"
84#include "MarkInfo.h"
85#include "mbtiles.h"
86#include "MUIBar.h"
87#include "navutil.h"
88#include "OCPN_AUIManager.h"
89#include "ocpndc.h"
90#include "ocpn_frame.h"
91#include "ocpn_pixel.h"
92#include "OCPNRegion.h"
93#include "options.h"
94#include "piano.h"
95#include "pluginmanager.h"
96#include "Quilt.h"
97#include "route_gui.h"
98#include "routemanagerdialog.h"
99#include "route_point_gui.h"
100#include "RoutePropDlgImpl.h"
101#include "s52plib.h"
102#include "s52utils.h"
103#include "S57QueryDialog.h"
104#include "s57chart.h" // for ArrayOfS57Obj
105#include "shapefile_basemap.h"
106#include "styles.h"
107#include "SystemCmdSound.h"
108#include "tcmgr.h"
109#include "TCWin.h"
110#include "thumbwin.h"
111#include "tide_time.h"
112#include "timers.h"
113#include "toolbar.h"
114#include "track_gui.h"
115#include "TrackPropDlg.h"
116#include "undo.h"
117
118#include "s57_ocpn_utils.h"
119
120#ifdef __ANDROID__
121#include "androidUTIL.h"
122#endif
123
124#ifdef ocpnUSE_GL
125#include "glChartCanvas.h"
126#include "notification_manager_gui.h"
128#endif
129
130#ifdef __VISUALC__
131#include <wx/msw/msvcrt.h>
132#endif
133
134#ifndef __WXMSW__
135#include <signal.h>
136#include <setjmp.h>
137#endif
138
139#ifdef __WXMSW__
140#define printf printf2
141
142int __cdecl printf2(const char *format, ...) {
143 char str[1024];
144
145 va_list argptr;
146 va_start(argptr, format);
147 int ret = vsnprintf(str, sizeof(str), format, argptr);
148 va_end(argptr);
149 OutputDebugStringA(str);
150 return ret;
151}
152#endif
153
154#if defined(__MSVC__) && (_MSC_VER < 1700)
155#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
156#endif
157
158// Define to enable the invocation of a temporary menubar by pressing the Alt
159// key. Not implemented for Windows XP, as it interferes with Alt-Tab
160// processing.
161#define OCPN_ALT_MENUBAR 1
162
163// Profiling support
164// #include "/usr/include/valgrind/callgrind.h"
165
166// ----------------------------------------------------------------------------
167// Useful Prototypes
168// ----------------------------------------------------------------------------
169extern ColorScheme global_color_scheme; // library dependence
170extern wxColor GetDimColor(wxColor c); // library dependence
171
172static bool g_bSmoothRecenter = true;
173static bool bDrawCurrentValues;
174
184static int mouse_x;
194static int mouse_y;
195static bool mouse_leftisdown;
196static bool g_brouteCreating;
197static int r_gamma_mult;
198static int g_gamma_mult;
199static int b_gamma_mult;
200static int gamma_state;
201static bool g_brightness_init;
202static int last_brightness;
203static wxGLContext *g_pGLcontext; // shared common context
204
205// "Curtain" mode parameters
206static wxDialog *g_pcurtain;
207
208static wxString g_lastS52PLIBPluginMessage;
209
210#define MIN_BRIGHT 10
211#define MAX_BRIGHT 100
212
213//------------------------------------------------------------------------------
214// ChartCanvas Implementation
215//------------------------------------------------------------------------------
216BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
217EVT_PAINT(ChartCanvas::OnPaint)
218EVT_ACTIVATE(ChartCanvas::OnActivate)
219EVT_SIZE(ChartCanvas::OnSize)
220#ifndef HAVE_WX_GESTURE_EVENTS
221EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
222#endif
223EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
224EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
225EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
226EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
227EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
228EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
229EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
230EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
231EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
232EVT_KEY_UP(ChartCanvas::OnKeyUp)
233EVT_CHAR(ChartCanvas::OnKeyChar)
234EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
235EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
236EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
237EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
238EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
239EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
240EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
241EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
242
243END_EVENT_TABLE()
244
245// Define a constructor for my canvas
246ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
247 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
248 m_nmea_log(nmea_log) {
249 parent_frame = (MyFrame *)frame; // save a pointer to parent
250 m_canvasIndex = canvasIndex;
251
252 pscratch_bm = NULL;
253
254 SetBackgroundColour(wxColour(0, 0, 0));
255 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
256 // color scheme change
257
258 m_groupIndex = 0;
259 m_bDrawingRoute = false;
260 m_bRouteEditing = false;
261 m_bMarkEditing = false;
262 m_bRoutePoinDragging = false;
263 m_bIsInRadius = false;
264 m_bMayToggleMenuBar = true;
265
266 m_bFollow = false;
267 m_bShowNavobjects = true;
268 m_bTCupdate = false;
269 m_bAppendingRoute = false; // was true in MSW, why??
270 pThumbDIBShow = NULL;
271 m_bShowCurrent = false;
272 m_bShowTide = false;
273 bShowingCurrent = false;
274 pCwin = NULL;
275 warp_flag = false;
276 m_bzooming = false;
277 m_b_paint_enable = true;
278 m_routeState = 0;
279
280 pss_overlay_bmp = NULL;
281 pss_overlay_mask = NULL;
282 m_bChartDragging = false;
283 m_bMeasure_Active = false;
284 m_bMeasure_DistCircle = false;
285 m_pMeasureRoute = NULL;
286 m_pTrackRolloverWin = NULL;
287 m_pRouteRolloverWin = NULL;
288 m_pAISRolloverWin = NULL;
289 m_bedge_pan = false;
290 m_disable_edge_pan = false;
291 m_dragoffsetSet = false;
292 m_bautofind = false;
293 m_bFirstAuto = true;
294 m_groupIndex = 0;
295 m_singleChart = NULL;
296 m_upMode = NORTH_UP_MODE;
297 m_bShowAIS = true;
298 m_bShowAISScaled = false;
299 m_timed_move_vp_active = false;
300
301 m_vLat = 0.;
302 m_vLon = 0.;
303
304 m_pCIWin = NULL;
305
306 m_pSelectedRoute = NULL;
307 m_pSelectedTrack = NULL;
308 m_pRoutePointEditTarget = NULL;
309 m_pFoundPoint = NULL;
310 m_pMouseRoute = NULL;
311 m_prev_pMousePoint = NULL;
312 m_pEditRouteArray = NULL;
313 m_pFoundRoutePoint = NULL;
314 m_FinishRouteOnKillFocus = true;
315
316 m_pRolloverRouteSeg = NULL;
317 m_pRolloverTrackSeg = NULL;
318 m_bsectors_shown = false;
319
320 m_bbrightdir = false;
321 r_gamma_mult = 1;
322 g_gamma_mult = 1;
323 b_gamma_mult = 1;
324
325 m_pos_image_user_day = NULL;
326 m_pos_image_user_dusk = NULL;
327 m_pos_image_user_night = NULL;
328 m_pos_image_user_grey_day = NULL;
329 m_pos_image_user_grey_dusk = NULL;
330 m_pos_image_user_grey_night = NULL;
331
332 m_zoom_factor = 1;
333 m_rotation_speed = 0;
334 m_mustmove = 0;
335
336 m_OSoffsetx = 0.;
337 m_OSoffsety = 0.;
338
339 m_pos_image_user_yellow_day = NULL;
340 m_pos_image_user_yellow_dusk = NULL;
341 m_pos_image_user_yellow_night = NULL;
342
343 SetOwnShipState(SHIP_INVALID);
344
345 undo = new Undo(this);
346
347 VPoint.Invalidate();
348
349 m_glcc = NULL;
350
351 m_focus_indicator_pix = 1;
352
353 m_pCurrentStack = NULL;
354 m_bpersistent_quilt = false;
355 m_piano_ctx_menu = NULL;
356 m_Compass = NULL;
357 m_NotificationsList = NULL;
358 m_notification_button = NULL;
359
360 g_ChartNotRenderScaleFactor = 2.0;
361 m_bShowScaleInStatusBar = true;
362
363 m_muiBar = NULL;
364 m_bShowScaleInStatusBar = false;
365 m_show_focus_bar = true;
366
367 m_bShowOutlines = false;
368 m_bDisplayGrid = false;
369 m_bShowDepthUnits = true;
370 m_encDisplayCategory = (int)STANDARD;
371
372 m_encShowLights = true;
373 m_encShowAnchor = true;
374 m_encShowDataQual = false;
375 m_bShowGPS = true;
376 m_pQuilt = new Quilt(this);
377 SetQuiltMode(true);
378 SetAlertString("");
379 m_sector_glat = 0;
380 m_sector_glon = 0;
381 g_PrintingInProgress = false;
382
383#ifdef HAVE_WX_GESTURE_EVENTS
384 m_oldVPSScale = -1.0;
385 m_popupWanted = false;
386 m_leftdown = false;
387#endif /* HAVE_WX_GESTURE_EVENTS */
388
389 SetupGlCanvas();
390
391 singleClickEventIsValid = false;
392
393 // Build the cursors
394
395 pCursorLeft = NULL;
396 pCursorRight = NULL;
397 pCursorUp = NULL;
398 pCursorDown = NULL;
399 pCursorArrow = NULL;
400 pCursorPencil = NULL;
401 pCursorCross = NULL;
402
403 RebuildCursors();
404
405 SetCursor(*pCursorArrow);
406
407 pPanTimer = new wxTimer(this, m_MouseDragging);
408 pPanTimer->Stop();
409
410 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
411 pMovementTimer->Stop();
412
413 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
414 pMovementStopTimer->Stop();
415
416 pRotDefTimer = new wxTimer(this, ROT_TIMER);
417 pRotDefTimer->Stop();
418
419 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
420 m_DoubleClickTimer->Stop();
421
422 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
423 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
424 m_chart_drag_inertia_active = false;
425
426 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
427 m_animationActive = false;
428
429 m_panx = m_pany = 0;
430 m_panspeed = 0;
431 m_panx_target_final = m_pany_target_final = 0;
432 m_panx_target_now = m_pany_target_now = 0;
433
434 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
435 pCurTrackTimer->Stop();
436 m_curtrack_timer_msec = 10;
437
438 m_wheelzoom_stop_oneshot = 0;
439 m_last_wheel_dir = 0;
440
441 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
442
443 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
444
445 m_rollover_popup_timer_msec = 20;
446
447 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
448
449 m_b_rot_hidef = true;
450
451 proute_bm = NULL;
452 m_prot_bm = NULL;
453
454 m_upMode = NORTH_UP_MODE;
455 m_bLookAhead = false;
456
457 // Set some benign initial values
458
459 m_cs = GLOBAL_COLOR_SCHEME_DAY;
460 VPoint.clat = 0;
461 VPoint.clon = 0;
462 VPoint.view_scale_ppm = 1;
463 VPoint.Invalidate();
464 m_nMeasureState = 0;
465
466 m_canvas_scale_factor = 1.;
467
468 m_canvas_width = 1000;
469
470 m_overzoomTextWidth = 0;
471 m_overzoomTextHeight = 0;
472
473 // Create the default world chart
474 pWorldBackgroundChart = new GSHHSChart;
475 gShapeBasemap.Reset();
476
477 // Create the default depth unit emboss maps
478 m_pEM_Feet = NULL;
479 m_pEM_Meters = NULL;
480 m_pEM_Fathoms = NULL;
481
482 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
483
484 m_pEM_OverZoom = NULL;
485 SetOverzoomFont();
486 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
487
488 // Build icons for tide/current points
489 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
490 m_bmTideDay =
491 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
492
493 // Dusk
494 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
495
496 // Night
497 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
498
499 // Build Dusk/Night ownship icons
500 double factor_dusk = 0.5;
501 double factor_night = 0.25;
502
503 // Red
504 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
505
506 int rimg_width = m_os_image_red_day.GetWidth();
507 int rimg_height = m_os_image_red_day.GetHeight();
508
509 m_os_image_red_dusk = m_os_image_red_day.Copy();
510 m_os_image_red_night = m_os_image_red_day.Copy();
511
512 for (int iy = 0; iy < rimg_height; iy++) {
513 for (int ix = 0; ix < rimg_width; ix++) {
514 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
515 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
516 m_os_image_red_day.GetGreen(ix, iy),
517 m_os_image_red_day.GetBlue(ix, iy));
518 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
519 hsv.value = hsv.value * factor_dusk;
520 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
521 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
522
523 hsv = wxImage::RGBtoHSV(rgb);
524 hsv.value = hsv.value * factor_night;
525 nrgb = wxImage::HSVtoRGB(hsv);
526 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
527 }
528 }
529 }
530
531 // Grey
532 m_os_image_grey_day =
533 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
534
535 int gimg_width = m_os_image_grey_day.GetWidth();
536 int gimg_height = m_os_image_grey_day.GetHeight();
537
538 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
539 m_os_image_grey_night = m_os_image_grey_day.Copy();
540
541 for (int iy = 0; iy < gimg_height; iy++) {
542 for (int ix = 0; ix < gimg_width; ix++) {
543 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
544 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
545 m_os_image_grey_day.GetGreen(ix, iy),
546 m_os_image_grey_day.GetBlue(ix, iy));
547 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
548 hsv.value = hsv.value * factor_dusk;
549 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
550 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
551
552 hsv = wxImage::RGBtoHSV(rgb);
553 hsv.value = hsv.value * factor_night;
554 nrgb = wxImage::HSVtoRGB(hsv);
555 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
556 }
557 }
558 }
559
560 // Yellow
561 m_os_image_yellow_day = m_os_image_red_day.Copy();
562
563 gimg_width = m_os_image_yellow_day.GetWidth();
564 gimg_height = m_os_image_yellow_day.GetHeight();
565
566 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
567 m_os_image_yellow_night = m_os_image_red_day.Copy();
568
569 for (int iy = 0; iy < gimg_height; iy++) {
570 for (int ix = 0; ix < gimg_width; ix++) {
571 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
572 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
573 m_os_image_yellow_day.GetGreen(ix, iy),
574 m_os_image_yellow_day.GetBlue(ix, iy));
575 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
576 hsv.hue += 60. / 360.; // shift to yellow
577 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
578 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
579
580 hsv = wxImage::RGBtoHSV(rgb);
581 hsv.value = hsv.value * factor_dusk;
582 hsv.hue += 60. / 360.; // shift to yellow
583 nrgb = wxImage::HSVtoRGB(hsv);
584 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
585
586 hsv = wxImage::RGBtoHSV(rgb);
587 hsv.hue += 60. / 360.; // shift to yellow
588 hsv.value = hsv.value * factor_night;
589 nrgb = wxImage::HSVtoRGB(hsv);
590 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
591 }
592 }
593 }
594
595 // Set initial pointers to ownship images
596 m_pos_image_red = &m_os_image_red_day;
597 m_pos_image_yellow = &m_os_image_yellow_day;
598 m_pos_image_grey = &m_os_image_grey_day;
599
600 SetUserOwnship();
601
602 m_pBrightPopup = NULL;
603
604#ifdef ocpnUSE_GL
605 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
606#endif
607
608 SetupGridFont();
609
610 m_Piano = new Piano(this);
611
612 m_bShowCompassWin = true;
613 m_Compass = new ocpnCompass(this);
614 m_Compass->SetScaleFactor(g_compass_scalefactor);
615 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
616
617 m_notification_button = new NotificationButton(this);
618 m_notification_button->SetScaleFactor(g_compass_scalefactor);
619 m_notification_button->Show(true);
620
621 m_pianoFrozen = false;
622
623 SetMinSize(wxSize(200, 200));
624
625 m_displayScale = 1.0;
626#if defined(__WXOSX__) || defined(__WXGTK3__)
627 // Support scaled HDPI displays.
628 m_displayScale = GetContentScaleFactor();
629#endif
630 VPoint.SetPixelScale(m_displayScale);
631
632#ifdef HAVE_WX_GESTURE_EVENTS
633 // if (!m_glcc)
634 {
635 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
636 wxLogError("Failed to enable touch events");
637 }
638
639 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
640
641 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
642 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
643
644 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
645 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
646
647 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
648 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
649
650 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
651 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
652 }
653#endif
654
655 // Listen for notification events
656 auto &noteman = NotificationManager::GetInstance();
657
658 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
659 evt_notificationlist_change_listener.Listen(
660 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
661 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
662 if (m_NotificationsList && m_NotificationsList->IsShown()) {
663 m_NotificationsList->ReloadNotificationList();
664 }
665 Refresh();
666 });
667}
668
669ChartCanvas::~ChartCanvas() {
670 delete pThumbDIBShow;
671
672 // Delete Cursors
673 delete pCursorLeft;
674 delete pCursorRight;
675 delete pCursorUp;
676 delete pCursorDown;
677 delete pCursorArrow;
678 delete pCursorPencil;
679 delete pCursorCross;
680
681 delete pPanTimer;
682 delete pMovementTimer;
683 delete pMovementStopTimer;
684 delete pCurTrackTimer;
685 delete pRotDefTimer;
686 delete m_DoubleClickTimer;
687
688 delete m_pTrackRolloverWin;
689 delete m_pRouteRolloverWin;
690 delete m_pAISRolloverWin;
691 delete m_pBrightPopup;
692
693 delete m_pCIWin;
694
695 delete pscratch_bm;
696
697 m_dc_route.SelectObject(wxNullBitmap);
698 delete proute_bm;
699
700 delete pWorldBackgroundChart;
701 delete pss_overlay_bmp;
702
703 delete m_pEM_Feet;
704 delete m_pEM_Meters;
705 delete m_pEM_Fathoms;
706
707 delete m_pEM_OverZoom;
708 // delete m_pEM_CM93Offset;
709
710 delete m_prot_bm;
711
712 delete m_pos_image_user_day;
713 delete m_pos_image_user_dusk;
714 delete m_pos_image_user_night;
715 delete m_pos_image_user_grey_day;
716 delete m_pos_image_user_grey_dusk;
717 delete m_pos_image_user_grey_night;
718 delete m_pos_image_user_yellow_day;
719 delete m_pos_image_user_yellow_dusk;
720 delete m_pos_image_user_yellow_night;
721
722 delete undo;
723#ifdef ocpnUSE_GL
724 if (!g_bdisable_opengl) {
725 delete m_glcc;
726
727#if wxCHECK_VERSION(2, 9, 0)
728 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
729#endif
730 }
731#endif
732
733 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
734 // wx tries to deliver events to this canvas during destroy.
735 MUIBar *muiBar = m_muiBar;
736 m_muiBar = 0;
737 delete muiBar;
738 delete m_pQuilt;
739 delete m_pCurrentStack;
740 delete m_Compass;
741 delete m_Piano;
742}
743
744void ChartCanvas::SetupGridFont() {
745 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
746 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
747 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
748 m_pgridFont = FontMgr::Get().FindOrCreateFont(
749 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
750 FALSE, wxString(_T ( "Arial" )));
751}
752
753void ChartCanvas::RebuildCursors() {
754 delete pCursorLeft;
755 delete pCursorRight;
756 delete pCursorUp;
757 delete pCursorDown;
758 delete pCursorArrow;
759 delete pCursorPencil;
760 delete pCursorCross;
761
762 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
763 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
764
765 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
766
767 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
768 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
769 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
770 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
771 wxImage ICursorPencil =
772 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
773 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
774
775#if !defined(__WXMSW__) && !defined(__WXQT__)
776 ICursorLeft.ConvertAlphaToMask(128);
777 ICursorRight.ConvertAlphaToMask(128);
778 ICursorUp.ConvertAlphaToMask(128);
779 ICursorDown.ConvertAlphaToMask(128);
780 ICursorPencil.ConvertAlphaToMask(10);
781 ICursorCross.ConvertAlphaToMask(10);
782#endif
783
784 if (ICursorLeft.Ok()) {
785 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
786 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
787 pCursorLeft = new wxCursor(ICursorLeft);
788 } else
789 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
790
791 if (ICursorRight.Ok()) {
792 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
793 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
794 pCursorRight = new wxCursor(ICursorRight);
795 } else
796 pCursorRight = new wxCursor(wxCURSOR_ARROW);
797
798 if (ICursorUp.Ok()) {
799 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
800 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
801 pCursorUp = new wxCursor(ICursorUp);
802 } else
803 pCursorUp = new wxCursor(wxCURSOR_ARROW);
804
805 if (ICursorDown.Ok()) {
806 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
807 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
808 pCursorDown = new wxCursor(ICursorDown);
809 } else
810 pCursorDown = new wxCursor(wxCURSOR_ARROW);
811
812 if (ICursorPencil.Ok()) {
813 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
814 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
815 pCursorPencil = new wxCursor(ICursorPencil);
816 } else
817 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
818
819 if (ICursorCross.Ok()) {
820 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
821 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
822 pCursorCross = new wxCursor(ICursorCross);
823 } else
824 pCursorCross = new wxCursor(wxCURSOR_ARROW);
825
826 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
827 pPlugIn_Cursor = NULL;
828}
829
830void ChartCanvas::CanvasApplyLocale() {
831 CreateDepthUnitEmbossMaps(m_cs);
832 CreateOZEmbossMapData(m_cs);
833}
834
835void ChartCanvas::SetupGlCanvas() {
836#ifndef __ANDROID__
837#ifdef ocpnUSE_GL
838 if (!g_bdisable_opengl) {
839 if (g_bopengl) {
840 wxLogMessage("Creating glChartCanvas");
841 m_glcc = new glChartCanvas(this);
842
843 // We use one context for all GL windows, so that textures etc will be
844 // automatically shared
845 if (IsPrimaryCanvas()) {
846 // qDebug() << "Creating Primary Context";
847
848 // wxGLContextAttrs ctxAttr;
849 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
850 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
851 // NULL, &ctxAttr);
852 wxGLContext *pctx = new wxGLContext(m_glcc);
853 m_glcc->SetContext(pctx);
854 g_pGLcontext = pctx; // Save a copy of the common context
855 } else {
856#ifdef __WXOSX__
857 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
858#else
859 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
860 // saved common context
861#endif
862 }
863 }
864 }
865#endif
866#endif
867
868#ifdef __ANDROID__ // ocpnUSE_GL
869 if (!g_bdisable_opengl) {
870 if (g_bopengl) {
871 // qDebug() << "SetupGlCanvas";
872 wxLogMessage("Creating glChartCanvas");
873
874 // We use one context for all GL windows, so that textures etc will be
875 // automatically shared
876 if (IsPrimaryCanvas()) {
877 qDebug() << "Creating Primary glChartCanvas";
878
879 // wxGLContextAttrs ctxAttr;
880 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
881 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
882 // NULL, &ctxAttr);
883 m_glcc = new glChartCanvas(this);
884
885 wxGLContext *pctx = new wxGLContext(m_glcc);
886 m_glcc->SetContext(pctx);
887 g_pGLcontext = pctx; // Save a copy of the common context
888 m_glcc->m_pParentCanvas = this;
889 // m_glcc->Reparent(this);
890 } else {
891 qDebug() << "Creating Secondary glChartCanvas";
892 // QGLContext *pctx =
893 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
894 // << "pctx: " << pctx;
895
896 m_glcc = new glChartCanvas(
897 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
898 // m_glcc = new glChartCanvas(this, pctx); //Shared
899 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
900 wxGLContext *pwxctx = new wxGLContext(m_glcc);
901 m_glcc->SetContext(pwxctx);
902 m_glcc->m_pParentCanvas = this;
903 // m_glcc->Reparent(this);
904 }
905 }
906 }
907#endif
908}
909
910void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
911 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
912
913 // On Android, we get a KillFocus on just about every keystroke.
914 // Why?
915#ifdef __ANDROID__
916 return;
917#endif
918
919 // Special logic:
920 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
921 // canvas focus. Why??? Who knows... So, we provide for this case by
922 // starting a timer if required to actually Finish() a route on a legitimate
923 // focus change, but not if the focus is quickly regained ( <20 msec.) on
924 // this canvas.
925#ifdef __WXOSX__
926 if (m_routeState && m_FinishRouteOnKillFocus)
927 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
928#else
929 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
930#endif
931}
932
933void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
934 m_routeFinishTimer.Stop();
935
936 // Try to keep the global top-line menubar selections up to date with the
937 // current "focus" canvas
938 gFrame->UpdateGlobalMenuItems(this);
939
940 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
941}
942
943void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
944 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
945}
946
947#ifdef HAVE_WX_GESTURE_EVENTS
948void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
949 /* we defer the popup menu call upon the leftup event
950 else the menu disappears immediately,
951 (see
952 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
953 */
954 m_popupWanted = true;
955}
956
957void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
958 // not implemented yet
959}
960
961void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
962
963void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
964
965void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
966 wxPoint pos = event.GetPosition();
967
968 m_leftdown = false;
969
970 if (!m_popupWanted) {
971 wxMouseEvent ev(wxEVT_LEFT_UP);
972 ev.m_x = pos.x;
973 ev.m_y = pos.y;
974 MouseEvent(ev);
975 return;
976 }
977
978 m_popupWanted = false;
979
980 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
981 ev.m_x = pos.x;
982 ev.m_y = pos.y;
983
984 MouseEvent(ev);
985}
986
987void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
988 m_leftdown = true;
989
990 wxPoint pos = event.GetPosition();
991 MouseEvent(event);
992}
993
994void ChartCanvas::OnMotion(wxMouseEvent &event) {
995 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
996 dragging, upon simple click, and without the OnLeftDown event before Thus,
997 this consists in skiping it, and setting the leftdown bit according to a
998 status that we trust */
999 event.m_leftDown = m_leftdown;
1000 MouseEvent(event);
1001}
1002
1003void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1004 /* there are spurious end zoom events upon right-click */
1005 if (event.IsGestureEnd()) return;
1006
1007 double factor = event.GetZoomFactor();
1008
1009 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1010 m_oldVPSScale = GetVPScale();
1011 }
1012
1013 double current_vps = GetVPScale();
1014 double wanted_factor = m_oldVPSScale / current_vps * factor;
1015
1016 ZoomCanvas(wanted_factor, true, false);
1017
1018 // Allow combined zoom/pan operation
1019 if (event.IsGestureStart()) {
1020 m_zoomStartPoint = event.GetPosition();
1021 } else {
1022 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1023 PanCanvas(-delta.x, -delta.y);
1024 m_zoomStartPoint = event.GetPosition();
1025 }
1026}
1027
1028void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1029
1030void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1031 DoRotateCanvas(0.0);
1032}
1033#endif /* HAVE_WX_GESTURE_EVENTS */
1034
1035void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1036 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1037 m_vLat = pcc->iLat;
1038 m_vLon = pcc->iLon;
1039
1040 m_restore_dbindex = pcc->DBindex;
1041 m_bFollow = pcc->bFollow;
1042 if (pcc->GroupID < 0) pcc->GroupID = 0;
1043
1044 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1045 m_groupIndex = 0;
1046 else
1047 m_groupIndex = pcc->GroupID;
1048
1049 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1050
1051 ShowTides(pcc->bShowTides);
1052 ShowCurrents(pcc->bShowCurrents);
1053
1054 SetShowDepthUnits(pcc->bShowDepthUnits);
1055 SetShowGrid(pcc->bShowGrid);
1056 SetShowOutlines(pcc->bShowOutlines);
1057
1058 SetShowAIS(pcc->bShowAIS);
1059 SetAttenAIS(pcc->bAttenAIS);
1060
1061 // ENC options
1062 SetShowENCText(pcc->bShowENCText);
1063 m_encDisplayCategory = pcc->nENCDisplayCategory;
1064 m_encShowDepth = pcc->bShowENCDepths;
1065 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1066 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1067 m_encShowLights = pcc->bShowENCLights;
1068 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1069 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1070 m_encShowDataQual = pcc->bShowENCDataQuality;
1071
1072 bool courseUp = pcc->bCourseUp;
1073 bool headUp = pcc->bHeadUp;
1074 m_upMode = NORTH_UP_MODE;
1075 if (courseUp)
1076 m_upMode = COURSE_UP_MODE;
1077 else if (headUp)
1078 m_upMode = HEAD_UP_MODE;
1079
1080 m_bLookAhead = pcc->bLookahead;
1081
1082 m_singleChart = NULL;
1083}
1084
1085void ChartCanvas::ApplyGlobalSettings() {
1086 // GPS compas window
1087 if (m_Compass) {
1088 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1089 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1090 }
1091 m_notification_button->UpdateStatus();
1092}
1093
1094void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1095 bool groupOK = CheckGroup(m_groupIndex);
1096
1097 if (!groupOK) {
1098 SetGroupIndex(m_groupIndex, true);
1099 }
1100}
1101
1102void ChartCanvas::SetShowGPS(bool bshow) {
1103 if (m_bShowGPS != bshow) {
1104 delete m_Compass;
1105 m_Compass = new ocpnCompass(this, bshow);
1106 m_Compass->SetScaleFactor(g_compass_scalefactor);
1107 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1108 }
1109 m_bShowGPS = bshow;
1110}
1111
1112void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1113 m_bShowCompassWin = bshow;
1114 if (m_Compass) {
1115 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1116 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1117 }
1118}
1119
1120int ChartCanvas::GetPianoHeight() {
1121 int height = 0;
1122 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1123
1124 return height;
1125}
1126
1127void ChartCanvas::ConfigureChartBar() {
1128 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1129
1130 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1131 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1132
1133 if (GetQuiltMode()) {
1134 m_Piano->SetRoundedRectangles(true);
1135 }
1136 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1137 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1138 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1139}
1140
1141void ChartCanvas::ShowTides(bool bShow) {
1142 gFrame->LoadHarmonics();
1143
1144 if (ptcmgr->IsReady()) {
1145 SetbShowTide(bShow);
1146
1147 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1148 } else {
1149 wxLogMessage("Chart1::Event...TCMgr Not Available");
1150 SetbShowTide(false);
1151 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1152 }
1153
1154 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1155 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1156
1157 // TODO
1158 // if( GetbShowTide() ) {
1159 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1160 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1161 // update
1162 // } else
1163 // FrameTCTimer.Stop();
1164}
1165
1166void ChartCanvas::ShowCurrents(bool bShow) {
1167 gFrame->LoadHarmonics();
1168
1169 if (ptcmgr->IsReady()) {
1170 SetbShowCurrent(bShow);
1171 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1172 } else {
1173 wxLogMessage("Chart1::Event...TCMgr Not Available");
1174 SetbShowCurrent(false);
1175 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1176 }
1177
1178 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1179 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1180
1181 // TODO
1182 // if( GetbShowCurrent() ) {
1183 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1184 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1185 // update
1186 // } else
1187 // FrameTCTimer.Stop();
1188}
1189
1190// TODO
1191static ChartDummy *pDummyChart;
1192
1195
1196void ChartCanvas::canvasRefreshGroupIndex(void) { SetGroupIndex(m_groupIndex); }
1197
1198void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1199 SetAlertString("");
1200
1201 int new_index = index;
1202 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1203
1204 bool bgroup_override = false;
1205 int old_group_index = new_index;
1206
1207 if (!CheckGroup(new_index)) {
1208 new_index = 0;
1209 bgroup_override = true;
1210 }
1211
1212 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1213 new_index = index;
1214
1215 // Get the currently displayed chart native scale, and the current ViewPort
1216 int current_chart_native_scale = GetCanvasChartNativeScale();
1217 ViewPort vp = GetVP();
1218
1219 m_groupIndex = new_index;
1220
1221 // Are there ENCs in this group
1222 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1223
1224 // Update the MUIBar for ENC availability
1225 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1226
1227 // Allow the chart database to pre-calculate the MBTile inclusion test
1228 // boolean...
1229 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1230
1231 // Invalidate the "sticky" chart on group change, since it might not be in
1232 // the new group
1233 g_sticky_chart = -1;
1234
1235 // We need a chartstack and quilt to figure out which chart to open in the
1236 // new group
1237 UpdateCanvasOnGroupChange();
1238
1239 int dbi_now = -1;
1240 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1241
1242 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1243
1244 // If a new reference chart is indicated, set a good scale for it.
1245 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1246 double best_scale = GetBestStartScale(dbi_hint, vp);
1247 SetVPScale(best_scale);
1248 }
1249
1250 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1251
1252 // Refresh the canvas, selecting the "best" chart,
1253 // applying the prior ViewPort exactly
1254 canvasChartsRefresh(dbi_hint);
1255
1256 UpdateCanvasControlBar();
1257
1258 if (!autoSwitch && bgroup_override) {
1259 // show a short timed message box
1260 wxString msg(_("Group \""));
1261
1262 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1263 msg += pGroup->m_group_name;
1264
1265 msg += _("\" is empty.");
1266
1267 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1268
1269 return;
1270 }
1271
1272 // Message box is deferred so that canvas refresh occurs properly before
1273 // dialog
1274 if (bgroup_override) {
1275 wxString msg(_("Group \""));
1276
1277 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1278 msg += pGroup->m_group_name;
1279
1280 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1281
1282 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1283 }
1284}
1285
1286bool ChartCanvas::CheckGroup(int igroup) {
1287 if (!ChartData) return true; // Not known yet...
1288
1289 if (igroup == 0) return true; // "all charts" is always OK
1290
1291 if (igroup < 0) // negative group is an error
1292 return false;
1293
1294 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1295
1296 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1297 // and auto-shift to group 0
1298 return false;
1299
1300 for (const auto &elem : pGroup->m_element_array) {
1301 for (unsigned int ic = 0;
1302 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1303 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1304 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1305
1306 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1307 }
1308 }
1309
1310 // If necessary, check for GSHHS
1311 for (const auto &elem : pGroup->m_element_array) {
1312 const wxString &element_root = elem.m_element_name;
1313 wxString test_string = "GSHH";
1314 if (element_root.Upper().Contains(test_string)) return true;
1315 }
1316
1317 return false;
1318}
1319
1320void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1321 if (!ChartData) return;
1322
1323 AbstractPlatform::ShowBusySpinner();
1324
1325 double old_scale = GetVPScale();
1326 InvalidateQuilt();
1327 SetQuiltRefChart(-1);
1328
1329 m_singleChart = NULL;
1330
1331 // delete m_pCurrentStack;
1332 // m_pCurrentStack = NULL;
1333
1334 // Build a new ChartStack
1335 if (!m_pCurrentStack) {
1336 m_pCurrentStack = new ChartStack;
1337 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1338 }
1339
1340 if (-1 != dbi_hint) {
1341 if (GetQuiltMode()) {
1342 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1343 SetQuiltRefChart(dbi_hint);
1344 } else {
1345 // Open the saved chart
1346 ChartBase *pTentative_Chart;
1347 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1348
1349 if (pTentative_Chart) {
1350 /* m_singleChart is always NULL here, (set above) should this go before
1351 * that? */
1352 if (m_singleChart) m_singleChart->Deactivate();
1353
1354 m_singleChart = pTentative_Chart;
1355 m_singleChart->Activate();
1356
1357 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1358 GetpCurrentStack(), m_singleChart->GetFullPath());
1359 }
1360 }
1361
1362 // refresh_Piano();
1363 } else {
1364 // Select reference chart from the stack, as though clicked by user
1365 // Make it the smallest scale chart on the stack
1366 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1367 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1368 SetQuiltRefChart(selected_index);
1369 }
1370
1371 // Validate the correct single chart, or set the quilt mode as appropriate
1372 SetupCanvasQuiltMode();
1373 if (!GetQuiltMode() && m_singleChart == 0) {
1374 // use a dummy like in DoChartUpdate
1375 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1376 m_singleChart = pDummyChart;
1377 SetVPScale(old_scale);
1378 }
1379
1380 ReloadVP();
1381
1382 UpdateCanvasControlBar();
1383 UpdateGPSCompassStatusBox(true);
1384
1385 SetCursor(wxCURSOR_ARROW);
1386
1387 AbstractPlatform::HideBusySpinner();
1388}
1389
1390bool ChartCanvas::DoCanvasUpdate(void) {
1391 double tLat, tLon; // Chart Stack location
1392 double vpLat, vpLon; // ViewPort location
1393 bool blong_jump = false;
1394 meters_to_shift = 0;
1395 dir_to_shift = 0;
1396
1397 bool bNewChart = false;
1398 bool bNewView = false;
1399 bool bCanvasChartAutoOpen = true; // debugging
1400
1401 bool bNewPiano = false;
1402 bool bOpenSpecified;
1403 ChartStack LastStack;
1404 ChartBase *pLast_Ch;
1405
1406 ChartStack WorkStack;
1407
1408 if (bDBUpdateInProgress) return false;
1409 if (!ChartData) return false;
1410
1411 if (ChartData->IsBusy()) return false;
1412 if (m_chart_drag_inertia_active) return false;
1413
1414 // Startup case:
1415 // Quilting is enabled, but the last chart seen was not quiltable
1416 // In this case, drop to single chart mode, set persistence flag,
1417 // And open the specified chart
1418 // TODO implement this
1419 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1420 // if( GetQuiltMode() ) {
1421 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1422 // gFrame->ToggleQuiltMode();
1423 // m_bpersistent_quilt = true;
1424 // m_singleChart = NULL;
1425 // }
1426 // }
1427 // }
1428
1429 // If in auto-follow mode, use the current glat,glon to build chart
1430 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1431 // other means
1432
1433 if (m_bFollow) {
1434 tLat = gLat;
1435 tLon = gLon;
1436
1437 // Set the ViewPort center based on the OWNSHIP offset
1438 double dx = m_OSoffsetx;
1439 double dy = m_OSoffsety;
1440 double d_east = dx / GetVP().view_scale_ppm;
1441 double d_north = dy / GetVP().view_scale_ppm;
1442
1443 if (GetUpMode() == NORTH_UP_MODE) {
1444 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1445 } else {
1446 double offset_angle = atan2(d_north, d_east);
1447 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1448 double chart_angle = GetVPRotation();
1449 double target_angle = chart_angle + offset_angle;
1450 double d_east_mod = offset_distance * cos(target_angle);
1451 double d_north_mod = offset_distance * sin(target_angle);
1452 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1453 }
1454
1455 // on lookahead mode, adjust the vp center point
1456 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1457 double cog_to_use = gCog;
1458 if (g_btenhertz &&
1459 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1460 cog_to_use = gCog_gt;
1461 blong_jump = true;
1462 }
1463 if (!g_btenhertz) cog_to_use = g_COGAvg;
1464
1465 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1466
1467 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1468 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1469
1470 double pixel_delta_tent =
1471 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1472
1473 double pixel_delta = 0;
1474
1475 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1476 // avoid jumping of the vp center point during slow maneuvering, or at
1477 // anchor....
1478 if (!std::isnan(gSog)) {
1479 if (gSog < 2.0)
1480 pixel_delta = 0.;
1481 else
1482 pixel_delta = pixel_delta_tent;
1483 }
1484
1485 meters_to_shift = 0;
1486 dir_to_shift = 0;
1487 if (!std::isnan(gCog)) {
1488 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1489 dir_to_shift = cog_to_use;
1490 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1491 &vpLon);
1492 } else {
1493 vpLat = gLat;
1494 vpLon = gLon;
1495 }
1496 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1497 m_OSoffsetx = 0; // center ownship on loss of GPS
1498 m_OSoffsety = 0;
1499 vpLat = gLat;
1500 vpLon = gLon;
1501 }
1502
1503 } else {
1504 tLat = m_vLat;
1505 tLon = m_vLon;
1506 vpLat = m_vLat;
1507 vpLon = m_vLon;
1508 }
1509
1510 if (GetQuiltMode()) {
1511 int current_db_index = -1;
1512 if (m_pCurrentStack)
1513 current_db_index =
1514 m_pCurrentStack
1515 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1516 // chart dbIndex
1517 else
1518 m_pCurrentStack = new ChartStack;
1519
1520 // This logic added to enable opening a chart when there is no
1521 // previous chart indication, either from inital startup, or from adding
1522 // new chart directory
1523 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1524 m_pCurrentStack) {
1525 if (m_pCurrentStack->nEntry) {
1526 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1527 1); // smallest scale
1528 SelectQuiltRefdbChart(new_dbIndex, true);
1529 m_bautofind = false;
1530 }
1531 }
1532
1533 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1534 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1535
1536 if (m_bFirstAuto) {
1537 // Allow the chart database to pre-calculate the MBTile inclusion test
1538 // boolean...
1539 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1540
1541 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1542 // physical pixels. On standard DPI displays where logical = physical
1543 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1544 // logical pixels, this ratio would be 0.5.
1545 double proposed_scale_onscreen =
1546 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1547
1548 int initial_db_index = m_restore_dbindex;
1549 if (initial_db_index < 0) {
1550 if (m_pCurrentStack->nEntry) {
1551 initial_db_index =
1552 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1553 } else
1554 m_bautofind = true; // initial_db_index = 0;
1555 }
1556
1557 if (m_pCurrentStack->nEntry) {
1558 int initial_type = ChartData->GetDBChartType(initial_db_index);
1559
1560 // Check to see if the target new chart is quiltable as a reference
1561 // chart
1562
1563 if (!IsChartQuiltableRef(initial_db_index)) {
1564 // If it is not quiltable, then walk the stack up looking for a
1565 // satisfactory chart i.e. one that is quiltable and of the same type
1566 // XXX if there's none?
1567 int stack_index = 0;
1568
1569 if (stack_index >= 0) {
1570 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1571 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1572 if (IsChartQuiltableRef(test_db_index) &&
1573 (initial_type ==
1574 ChartData->GetDBChartType(initial_db_index))) {
1575 initial_db_index = test_db_index;
1576 break;
1577 }
1578 stack_index++;
1579 }
1580 }
1581 }
1582
1583 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1584 if (pc) {
1585 SetQuiltRefChart(initial_db_index);
1586 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1587 }
1588 }
1589 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1590 // just GetVPScale(), so I'm not sure why it's necessary to define the
1591 // proposed_scale_onscreen variable.
1592 bNewView |= SetViewPoint(vpLat, vpLon,
1593 GetCanvasScaleFactor() / proposed_scale_onscreen,
1594 0, GetVPRotation());
1595 }
1596 // Measure rough jump distance if in bfollow mode
1597 // No good reason to do smooth pan for
1598 // jump distance more than one screen width at scale.
1599 bool super_jump = false;
1600 if (m_bFollow) {
1601 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1602 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1603 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1604 }
1605#if 0
1606 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead && !g_btouch && !m_bzooming) {
1607 int nstep = 5;
1608 if (blong_jump) nstep = 20;
1609 StartTimedMovementVP(vpLat, vpLon, nstep);
1610 } else
1611#endif
1612 {
1613 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1614 }
1615
1616 goto update_finish;
1617 }
1618
1619 // Single Chart Mode from here....
1620 pLast_Ch = m_singleChart;
1621 ChartTypeEnum new_open_type;
1622 ChartFamilyEnum new_open_family;
1623 if (pLast_Ch) {
1624 new_open_type = pLast_Ch->GetChartType();
1625 new_open_family = pLast_Ch->GetChartFamily();
1626 } else {
1627 new_open_type = CHART_TYPE_KAP;
1628 new_open_family = CHART_FAMILY_RASTER;
1629 }
1630
1631 bOpenSpecified = m_bFirstAuto;
1632
1633 // Make sure the target stack is valid
1634 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1635
1636 // Build a chart stack based on tLat, tLon
1637 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1638 m_groupIndex)) { // Bogus Lat, Lon?
1639 if (NULL == pDummyChart) {
1640 pDummyChart = new ChartDummy;
1641 bNewChart = true;
1642 }
1643
1644 if (m_singleChart)
1645 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1646
1647 m_singleChart = pDummyChart;
1648
1649 // If the current viewpoint is invalid, set the default scale to
1650 // something reasonable.
1651 double set_scale = GetVPScale();
1652 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1653
1654 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1655
1656 // If the chart stack has just changed, there is new status
1657 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1658 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1659 bNewPiano = true;
1660 bNewChart = true;
1661 }
1662 }
1663
1664 // Copy the new (by definition empty) stack into the target stack
1665 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1666
1667 goto update_finish;
1668 }
1669
1670 // Check to see if Chart Stack has changed
1671 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1672 // New chart stack, so...
1673 bNewPiano = true;
1674
1675 // Save a copy of the current stack
1676 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1677
1678 // Copy the new stack into the target stack
1679 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1680
1681 // Is Current Chart in new stack?
1682
1683 int tEntry = -1;
1684 if (NULL != m_singleChart) // this handles startup case
1685 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1686 m_singleChart->GetFullPath());
1687
1688 if (tEntry != -1) { // m_singleChart is in the new stack
1689 m_pCurrentStack->CurrentStackEntry = tEntry;
1690 bNewChart = false;
1691 }
1692
1693 else // m_singleChart is NOT in new stack
1694 { // So, need to open a new chart
1695 // Find the largest scale raster chart that opens OK
1696
1697 ChartBase *pProposed = NULL;
1698
1699 if (bCanvasChartAutoOpen) {
1700 bool search_direction =
1701 false; // default is to search from lowest to highest
1702 int start_index = 0;
1703
1704 // A special case: If panning at high scale, open largest scale
1705 // chart first
1706 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1707 (LastStack.nEntry == 0)) {
1708 search_direction = true;
1709 start_index = m_pCurrentStack->nEntry - 1;
1710 }
1711
1712 // Another special case, open specified index on program start
1713 if (bOpenSpecified) {
1714 search_direction = false;
1715 start_index = 0;
1716 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1717 start_index = 0;
1718
1719 new_open_type = CHART_TYPE_DONTCARE;
1720 }
1721
1722 pProposed = ChartData->OpenStackChartConditional(
1723 m_pCurrentStack, start_index, search_direction, new_open_type,
1724 new_open_family);
1725
1726 // Try to open other types/families of chart in some priority
1727 if (NULL == pProposed)
1728 pProposed = ChartData->OpenStackChartConditional(
1729 m_pCurrentStack, start_index, search_direction,
1730 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1731
1732 if (NULL == pProposed)
1733 pProposed = ChartData->OpenStackChartConditional(
1734 m_pCurrentStack, start_index, search_direction,
1735 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1736
1737 bNewChart = true;
1738
1739 } // bCanvasChartAutoOpen
1740
1741 else
1742 pProposed = NULL;
1743
1744 // If no go, then
1745 // Open a Dummy Chart
1746 if (NULL == pProposed) {
1747 if (NULL == pDummyChart) {
1748 pDummyChart = new ChartDummy;
1749 bNewChart = true;
1750 }
1751
1752 if (pLast_Ch)
1753 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1754
1755 pProposed = pDummyChart;
1756 }
1757
1758 // Arriving here, pProposed points to an opened chart, or NULL.
1759 if (m_singleChart) m_singleChart->Deactivate();
1760 m_singleChart = pProposed;
1761
1762 if (m_singleChart) {
1763 m_singleChart->Activate();
1764 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1765 m_pCurrentStack, m_singleChart->GetFullPath());
1766 }
1767 } // need new chart
1768
1769 // Arriving here, m_singleChart is opened and OK, or NULL
1770 if (NULL != m_singleChart) {
1771 // Setup the view using the current scale
1772 double set_scale = GetVPScale();
1773
1774 // If the current viewpoint is invalid, set the default scale to
1775 // something reasonable.
1776 if (!GetVP().IsValid())
1777 set_scale = 1. / 20000.;
1778 else { // otherwise, match scale if elected.
1779 double proposed_scale_onscreen;
1780
1781 if (m_bFollow) { // autoset the scale only if in autofollow
1782 double new_scale_ppm =
1783 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1784 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1785 } else
1786 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1787
1788 // This logic will bring a new chart onscreen at roughly twice the true
1789 // paper scale equivalent. Note that first chart opened on application
1790 // startup (bOpenSpecified = true) will open at the config saved scale
1791 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1792 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1793 double equivalent_vp_scale =
1794 GetCanvasScaleFactor() / proposed_scale_onscreen;
1795 double new_scale_ppm =
1796 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1797 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1798 }
1799
1800 if (m_bFollow) { // bounds-check the scale only if in autofollow
1801 proposed_scale_onscreen =
1802 wxMin(proposed_scale_onscreen,
1803 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1804 GetCanvasWidth()));
1805 proposed_scale_onscreen =
1806 wxMax(proposed_scale_onscreen,
1807 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1809 }
1810
1811 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1812 }
1813
1814 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1815 m_singleChart->GetChartSkew() * PI / 180.,
1816 GetVPRotation());
1817 }
1818 } // new stack
1819
1820 else // No change in Chart Stack
1821 {
1822 if ((m_bFollow) && m_singleChart)
1823 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1824 m_singleChart->GetChartSkew() * PI / 180.,
1825 GetVPRotation());
1826 }
1827
1828update_finish:
1829
1830 // TODO
1831 // if( bNewPiano ) UpdateControlBar();
1832
1833 m_bFirstAuto = false; // Auto open on program start
1834
1835 // If we need a Refresh(), do it here...
1836 // But don't duplicate a Refresh() done by SetViewPoint()
1837 if (bNewChart && !bNewView) Refresh(false);
1838
1839#ifdef ocpnUSE_GL
1840 // If a new chart, need to invalidate gl viewport for refresh
1841 // so the fbo gets flushed
1842 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1843#endif
1844
1845 return bNewChart | bNewView;
1846}
1847
1848void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1849 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1850
1851 SetQuiltRefChart(db_index);
1852 if (ChartData) {
1853 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1854 if (pc) {
1855 if (b_autoscale) {
1856 double best_scale_ppm = GetBestVPScale(pc);
1857 SetVPScale(best_scale_ppm);
1858 }
1859 } else
1860 SetQuiltRefChart(-1);
1861 } else
1862 SetQuiltRefChart(-1);
1863}
1864
1865void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1866 std::vector<int> piano_chart_index_array =
1867 GetQuiltExtendedStackdbIndexArray();
1868 int current_db_index = piano_chart_index_array[selected_index];
1869
1870 SelectQuiltRefdbChart(current_db_index);
1871}
1872
1873double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1874 if (pchart) {
1875 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1876
1877 if ((g_bPreserveScaleOnX) ||
1878 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
1879 double new_scale_ppm = GetVPScale();
1880 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1881 } else {
1882 // This logic will bring the new chart onscreen at roughly twice the true
1883 // paper scale equivalent.
1884 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
1885 double equivalent_vp_scale =
1886 GetCanvasScaleFactor() / proposed_scale_onscreen;
1887 double new_scale_ppm =
1888 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1889 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1890 }
1891
1892 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
1893 // set. Otherwise, we get severe performance problems on all platforms
1894
1895 double max_underzoom_multiplier = 2.0;
1896 if (GetVP().b_quilt) {
1897 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
1898 pchart->GetChartType(),
1899 pchart->GetChartFamily());
1900 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
1901 }
1902
1903 proposed_scale_onscreen = wxMin(
1904 proposed_scale_onscreen,
1905 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
1906 max_underzoom_multiplier);
1907
1908 // And, do not allow excessive overzoom either
1909 proposed_scale_onscreen =
1910 wxMax(proposed_scale_onscreen,
1911 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
1912
1913 return GetCanvasScaleFactor() / proposed_scale_onscreen;
1914 } else
1915 return 1.0;
1916}
1917
1918void ChartCanvas::SetupCanvasQuiltMode(void) {
1919 if (GetQuiltMode()) // going to quilt mode
1920 {
1921 ChartData->LockCache();
1922
1923 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
1924
1925 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1926
1927 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1928 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1929 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1930 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1931
1932 m_Piano->SetRoundedRectangles(true);
1933
1934 // Select the proper Ref chart
1935 int target_new_dbindex = -1;
1936 if (m_pCurrentStack) {
1937 target_new_dbindex =
1938 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
1939
1940 if (-1 != target_new_dbindex) {
1941 if (!IsChartQuiltableRef(target_new_dbindex)) {
1942 int proj = ChartData->GetDBChartProj(target_new_dbindex);
1943 int type = ChartData->GetDBChartType(target_new_dbindex);
1944
1945 // walk the stack up looking for a satisfactory chart
1946 int stack_index = m_pCurrentStack->CurrentStackEntry;
1947
1948 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
1949 (stack_index >= 0)) {
1950 int proj_tent = ChartData->GetDBChartProj(
1951 m_pCurrentStack->GetDBIndex(stack_index));
1952 int type_tent = ChartData->GetDBChartType(
1953 m_pCurrentStack->GetDBIndex(stack_index));
1954
1955 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
1956 if ((proj == proj_tent) && (type_tent == type)) {
1957 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
1958 break;
1959 }
1960 }
1961 stack_index++;
1962 }
1963 }
1964 }
1965 }
1966
1967 if (IsChartQuiltableRef(target_new_dbindex))
1968 SelectQuiltRefdbChart(target_new_dbindex,
1969 false); // Try not to allow a scale change
1970 else
1971 SelectQuiltRefdbChart(-1, false);
1972
1973 m_singleChart = NULL; // Bye....
1974
1975 // Re-qualify the quilt reference chart selection
1976 AdjustQuiltRefChart();
1977
1978 // Restore projection type saved on last quilt mode toggle
1979 // TODO
1980 // if(g_sticky_projection != -1)
1981 // GetVP().SetProjectionType(g_sticky_projection);
1982 // else
1983 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
1984 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
1985
1986 } else // going to SC Mode
1987 {
1988 std::vector<int> empty_array;
1989 m_Piano->SetActiveKeyArray(empty_array);
1990 m_Piano->SetNoshowIndexArray(empty_array);
1991 m_Piano->SetEclipsedIndexArray(empty_array);
1992
1993 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1994 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1995 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1996 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1997 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1998
1999 m_Piano->SetRoundedRectangles(false);
2000 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2001 }
2002
2003 // When shifting from quilt to single chart mode, select the "best" single
2004 // chart to show
2005 if (!GetQuiltMode()) {
2006 if (ChartData && ChartData->IsValid()) {
2007 UnlockQuilt();
2008
2009 double tLat, tLon;
2010 if (m_bFollow == true) {
2011 tLat = gLat;
2012 tLon = gLon;
2013 } else {
2014 tLat = m_vLat;
2015 tLon = m_vLon;
2016 }
2017
2018 if (!m_singleChart) {
2019 // Build a temporary chart stack based on tLat, tLon
2020 ChartStack TempStack;
2021 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2022 m_groupIndex);
2023
2024 // Iterate over the quilt charts actually shown, looking for the
2025 // largest scale chart that will be in the new chartstack.... This
2026 // will (almost?) always be the reference chart....
2027
2028 ChartBase *Candidate_Chart = NULL;
2029 int cur_max_scale = (int)1e8;
2030
2031 ChartBase *pChart = GetFirstQuiltChart();
2032 while (pChart) {
2033 // Is this pChart in new stack?
2034 int tEntry =
2035 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2036 if (tEntry != -1) {
2037 if (pChart->GetNativeScale() < cur_max_scale) {
2038 Candidate_Chart = pChart;
2039 cur_max_scale = pChart->GetNativeScale();
2040 }
2041 }
2042 pChart = GetNextQuiltChart();
2043 }
2044
2045 m_singleChart = Candidate_Chart;
2046
2047 // If the quilt is empty, there is no "best" chart.
2048 // So, open the smallest scale chart in the current stack
2049 if (NULL == m_singleChart) {
2050 m_singleChart = ChartData->OpenStackChartConditional(
2051 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2052 CHART_FAMILY_DONTCARE);
2053 }
2054 }
2055
2056 // Invalidate all the charts in the quilt,
2057 // as any cached data may be region based and not have fullscreen coverage
2058 InvalidateAllQuiltPatchs();
2059
2060 if (m_singleChart) {
2061 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2062 std::vector<int> one_array;
2063 one_array.push_back(dbi);
2064 m_Piano->SetActiveKeyArray(one_array);
2065 }
2066
2067 if (m_singleChart) {
2068 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2069 }
2070 }
2071 // Invalidate the current stack so that it will be rebuilt on next tick
2072 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2073 }
2074}
2075
2076bool ChartCanvas::IsTempMenuBarEnabled() {
2077#ifdef __WXMSW__
2078 int major;
2079 wxGetOsVersion(&major);
2080 return (major >
2081 5); // For Windows, function is only available on Vista and above
2082#else
2083 return true;
2084#endif
2085}
2086
2087double ChartCanvas::GetCanvasRangeMeters() {
2088 int width, height;
2089 GetSize(&width, &height);
2090 int minDimension = wxMin(width, height);
2091
2092 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2093 range *= cos(GetVP().clat * PI / 180.);
2094 return range;
2095}
2096
2097void ChartCanvas::SetCanvasRangeMeters(double range) {
2098 int width, height;
2099 GetSize(&width, &height);
2100 int minDimension = wxMin(width, height);
2101
2102 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2103 SetVPScale(scale_ppm / 2);
2104}
2105
2106bool ChartCanvas::SetUserOwnship() {
2107 // Look for user defined ownship image
2108 // This may be found in the shared data location along with other user
2109 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2110 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2111 double factor_dusk = 0.5;
2112 double factor_night = 0.25;
2113
2114 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2115 m_pos_image_user_day = new wxImage;
2116 *m_pos_image_user_day = pbmp->ConvertToImage();
2117 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2118
2119 int gimg_width = m_pos_image_user_day->GetWidth();
2120 int gimg_height = m_pos_image_user_day->GetHeight();
2121
2122 // Make dusk and night images
2123 m_pos_image_user_dusk = new wxImage;
2124 m_pos_image_user_night = new wxImage;
2125
2126 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2127 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2128
2129 for (int iy = 0; iy < gimg_height; iy++) {
2130 for (int ix = 0; ix < gimg_width; ix++) {
2131 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2132 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2133 m_pos_image_user_day->GetGreen(ix, iy),
2134 m_pos_image_user_day->GetBlue(ix, iy));
2135 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2136 hsv.value = hsv.value * factor_dusk;
2137 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2138 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2139 nrgb.blue);
2140
2141 hsv = wxImage::RGBtoHSV(rgb);
2142 hsv.value = hsv.value * factor_night;
2143 nrgb = wxImage::HSVtoRGB(hsv);
2144 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2145 nrgb.blue);
2146 }
2147 }
2148 }
2149
2150 // Make some alternate greyed out day/dusk/night images
2151 m_pos_image_user_grey_day = new wxImage;
2152 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2153
2154 m_pos_image_user_grey_dusk = new wxImage;
2155 m_pos_image_user_grey_night = new wxImage;
2156
2157 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2158 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2159
2160 for (int iy = 0; iy < gimg_height; iy++) {
2161 for (int ix = 0; ix < gimg_width; ix++) {
2162 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2163 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2164 m_pos_image_user_grey_day->GetGreen(ix, iy),
2165 m_pos_image_user_grey_day->GetBlue(ix, iy));
2166 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2167 hsv.value = hsv.value * factor_dusk;
2168 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2169 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2170 nrgb.blue);
2171
2172 hsv = wxImage::RGBtoHSV(rgb);
2173 hsv.value = hsv.value * factor_night;
2174 nrgb = wxImage::HSVtoRGB(hsv);
2175 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2176 nrgb.blue);
2177 }
2178 }
2179 }
2180
2181 // Make a yellow image for rendering under low accuracy chart conditions
2182 m_pos_image_user_yellow_day = new wxImage;
2183 m_pos_image_user_yellow_dusk = new wxImage;
2184 m_pos_image_user_yellow_night = new wxImage;
2185
2186 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2187 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2188 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2189
2190 for (int iy = 0; iy < gimg_height; iy++) {
2191 for (int ix = 0; ix < gimg_width; ix++) {
2192 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2193 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2194 m_pos_image_user_grey_day->GetGreen(ix, iy),
2195 m_pos_image_user_grey_day->GetBlue(ix, iy));
2196
2197 // Simply remove all "blue" from the greyscaled image...
2198 // so, what is not black becomes yellow.
2199 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2200 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2201 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2202
2203 hsv = wxImage::RGBtoHSV(rgb);
2204 hsv.value = hsv.value * factor_dusk;
2205 nrgb = wxImage::HSVtoRGB(hsv);
2206 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2207
2208 hsv = wxImage::RGBtoHSV(rgb);
2209 hsv.value = hsv.value * factor_night;
2210 nrgb = wxImage::HSVtoRGB(hsv);
2211 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2212 0);
2213 }
2214 }
2215 }
2216
2217 return true;
2218 } else
2219 return false;
2220}
2221
2223 m_display_size_mm = size;
2224
2225 // int sx, sy;
2226 // wxDisplaySize( &sx, &sy );
2227
2228 // Calculate logical pixels per mm for later reference.
2229 wxSize sd = g_Platform->getDisplaySize();
2230 double horizontal = sd.x;
2231 // Set DPI (Win) scale factor
2232 g_scaler = g_Platform->GetDisplayDIPMult(this);
2233
2234 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2235 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2236
2237 if (ps52plib) {
2238 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2239 ps52plib->SetPPMM(m_pix_per_mm);
2240 }
2241
2242 wxString msg;
2243 msg.Printf(
2244 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2245 "%d:%d ",
2246 m_display_size_mm, sd.x, sd.y);
2247 wxLogMessage(msg);
2248
2249 int ssx, ssy;
2250 ssx = g_monitor_info[g_current_monitor].width;
2251 ssy = g_monitor_info[g_current_monitor].height;
2252 msg.Printf("monitor size: %d %d", ssx, ssy);
2253 wxLogMessage(msg);
2254
2255 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2256}
2257#if 0
2258void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2259{
2260 wxString msg(event.m_string.c_str(), wxConvUTF8);
2261 // if cpus are removed between runs
2262 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2263 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2264 }
2265
2266 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2267 {
2268 compress_msg_array.RemoveAt(event.thread);
2269 compress_msg_array.Insert( msg, event.thread);
2270 }
2271 else
2272 compress_msg_array.Add(msg);
2273
2274
2275 wxString combined_msg;
2276 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2277 combined_msg += compress_msg_array[i];
2278 combined_msg += "\n";
2279 }
2280
2281 bool skip = false;
2282 pprog->Update(pprog_count, combined_msg, &skip );
2283 pprog->SetSize(pprog_size);
2284 if(skip)
2285 b_skipout = skip;
2286}
2287#endif
2288void ChartCanvas::InvalidateGL() {
2289 if (!m_glcc) return;
2290#ifdef ocpnUSE_GL
2291 if (g_bopengl) m_glcc->Invalidate();
2292#endif
2293 if (m_Compass) m_Compass->UpdateStatus(true);
2294}
2295
2296int ChartCanvas::GetCanvasChartNativeScale() {
2297 int ret = 1;
2298 if (!VPoint.b_quilt) {
2299 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2300 } else
2301 ret = (int)m_pQuilt->GetRefNativeScale();
2302
2303 return ret;
2304}
2305
2306ChartBase *ChartCanvas::GetChartAtCursor() {
2307 ChartBase *target_chart;
2308 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2309 target_chart = m_singleChart;
2310 else if (VPoint.b_quilt)
2311 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2312 else
2313 target_chart = NULL;
2314 return target_chart;
2315}
2316
2317ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2318 ChartBase *target_chart;
2319 if (VPoint.b_quilt)
2320 target_chart =
2321 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2322 else
2323 target_chart = NULL;
2324 return target_chart;
2325}
2326
2327int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2328 int new_dbIndex = -1;
2329 if (!VPoint.b_quilt) {
2330 if (m_pCurrentStack) {
2331 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2332 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2333 if (sc >= scale) {
2334 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2335 break;
2336 }
2337 }
2338 }
2339 } else {
2340 // Using the current quilt, select a useable reference chart
2341 // Said chart will be in the extended (possibly full-screen) stack,
2342 // And will have a scale equal to or just greater than the stipulated
2343 // value
2344 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2345 if (im > 0) {
2346 for (unsigned int is = 0; is < im; is++) {
2347 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2348 m_pQuilt->GetExtendedStackIndexArray()[is]);
2349 if ((m.Scale_ge(
2350 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2351 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2352 break;
2353 }
2354 }
2355 }
2356 }
2357
2358 return new_dbIndex;
2359}
2360
2361void ChartCanvas::EnablePaint(bool b_enable) {
2362 m_b_paint_enable = b_enable;
2363#ifdef ocpnUSE_GL
2364 if (m_glcc) m_glcc->EnablePaint(b_enable);
2365#endif
2366}
2367
2368bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2369
2370void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2371
2372std::vector<int> ChartCanvas::GetQuiltIndexArray(void) {
2373 return m_pQuilt->GetQuiltIndexArray();
2374 ;
2375}
2376
2377void ChartCanvas::SetQuiltMode(bool b_quilt) {
2378 VPoint.b_quilt = b_quilt;
2379 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2380}
2381
2382bool ChartCanvas::GetQuiltMode(void) { return VPoint.b_quilt; }
2383
2384int ChartCanvas::GetQuiltReferenceChartIndex(void) {
2385 return m_pQuilt->GetRefChartdbIndex();
2386}
2387
2388void ChartCanvas::InvalidateAllQuiltPatchs(void) {
2389 m_pQuilt->InvalidateAllQuiltPatchs();
2390}
2391
2392ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2393 return m_pQuilt->GetLargestScaleChart();
2394}
2395
2396ChartBase *ChartCanvas::GetFirstQuiltChart() {
2397 return m_pQuilt->GetFirstChart();
2398}
2399
2400ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2401
2402int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2403
2404void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2405 m_pQuilt->SetHiliteIndex(dbIndex);
2406}
2407
2408void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2409 m_pQuilt->SetHiliteIndexArray(hilite_array);
2410}
2411
2412void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2413 m_pQuilt->ClearHiliteIndexArray();
2414}
2415
2416std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2417 bool flag2) {
2418 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2419}
2420
2421int ChartCanvas::GetQuiltRefChartdbIndex(void) {
2422 return m_pQuilt->GetRefChartdbIndex();
2423}
2424
2425std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2426 return m_pQuilt->GetExtendedStackIndexArray();
2427}
2428
2429std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2430 return m_pQuilt->GetFullscreenIndexArray();
2431}
2432
2433std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2434 return m_pQuilt->GetEclipsedStackIndexArray();
2435}
2436
2437void ChartCanvas::InvalidateQuilt(void) { return m_pQuilt->Invalidate(); }
2438
2439double ChartCanvas::GetQuiltMaxErrorFactor() {
2440 return m_pQuilt->GetMaxErrorFactor();
2441}
2442
2443bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2444 return m_pQuilt->IsChartQuiltableRef(db_index);
2445}
2446
2447bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2448 double chartMaxScale =
2449 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2450 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2451}
2452
2453void ChartCanvas::StartMeasureRoute() {
2454 if (!m_routeState) { // no measure tool if currently creating route
2455 if (m_bMeasure_Active) {
2456 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2457 m_pMeasureRoute = NULL;
2458 }
2459
2460 m_bMeasure_Active = true;
2461 m_nMeasureState = 1;
2462 m_bDrawingRoute = false;
2463
2464 SetCursor(*pCursorPencil);
2465 Refresh();
2466 }
2467}
2468
2469void ChartCanvas::CancelMeasureRoute() {
2470 m_bMeasure_Active = false;
2471 m_nMeasureState = 0;
2472 m_bDrawingRoute = false;
2473
2474 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2475 m_pMeasureRoute = NULL;
2476
2477 SetCursor(*pCursorArrow);
2478}
2479
2480ViewPort &ChartCanvas::GetVP() { return VPoint; }
2481
2482void ChartCanvas::SetVP(ViewPort &vp) {
2483 VPoint = vp;
2484 VPoint.SetPixelScale(m_displayScale);
2485}
2486
2487// void ChartCanvas::SetFocus()
2488// {
2489// printf("set %d\n", m_canvasIndex);
2490// //wxWindow:SetFocus();
2491// }
2492
2493void ChartCanvas::TriggerDeferredFocus() {
2494 // #if defined(__WXGTK__) || defined(__WXOSX__)
2495
2496 m_deferredFocusTimer.Start(20, true);
2497
2498#if defined(__WXGTK__) || defined(__WXOSX__)
2499 gFrame->Raise();
2500#endif
2501
2502 // gFrame->Raise();
2503 // #else
2504 // SetFocus();
2505 // Refresh(true);
2506 // #endif
2507}
2508
2509void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2510 SetFocus();
2511 Refresh(true);
2512}
2513
2514void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2515 if (SendKeyEventToPlugins(event))
2516 return; // PlugIn did something, and does not want the canvas to do
2517 // anything else
2518
2519 int key_char = event.GetKeyCode();
2520 switch (key_char) {
2521 case '?':
2522 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2523 break;
2524 case '+':
2525 ZoomCanvas(g_plus_minus_zoom_factor, false);
2526 break;
2527 case '-':
2528 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2529 break;
2530 default:
2531 break;
2532 }
2533 if (g_benable_rotate) {
2534 switch (key_char) {
2535 case ']':
2536 RotateCanvas(1);
2537 Refresh();
2538 break;
2539
2540 case '[':
2541 RotateCanvas(-1);
2542 Refresh();
2543 break;
2544
2545 case '\\':
2546 DoRotateCanvas(0);
2547 break;
2548 }
2549 }
2550
2551 event.Skip();
2552}
2553
2554void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2555 if (SendKeyEventToPlugins(event))
2556 return; // PlugIn did something, and does not want the canvas to do
2557 // anything else
2558
2559 bool b_handled = false;
2560
2561 m_modkeys = event.GetModifiers();
2562
2563 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2564
2565#ifdef OCPN_ALT_MENUBAR
2566#ifndef __WXOSX__
2567 // If the permanent menubar is disabled, we show it temporarily when Alt is
2568 // pressed or when Alt + a letter is presssed (for the top-menu-level
2569 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2570 // some special cases.
2571 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2572 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2573 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2574 if (!g_bTempShowMenuBar) {
2575 g_bTempShowMenuBar = true;
2576 parent_frame->ApplyGlobalSettings(false);
2577 }
2578 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2579 event.Skip();
2580 return;
2581 }
2582 // If another key is pressed while Alt is down, do NOT toggle the menus when
2583 // Alt is released
2584 if (event.GetKeyCode() != WXK_ALT) {
2585 m_bMayToggleMenuBar = false;
2586 }
2587 }
2588#endif
2589#endif
2590
2591 // HOTKEYS
2592 switch (event.GetKeyCode()) {
2593 case WXK_TAB:
2594 // parent_frame->SwitchKBFocus( this );
2595 break;
2596
2597 case WXK_MENU:
2598 int x, y;
2599 event.GetPosition(&x, &y);
2600 m_FinishRouteOnKillFocus = false;
2601 CallPopupMenu(x, y);
2602 m_FinishRouteOnKillFocus = true;
2603 break;
2604
2605 case WXK_ALT:
2606 m_modkeys |= wxMOD_ALT;
2607 break;
2608
2609 case WXK_CONTROL:
2610 m_modkeys |= wxMOD_CONTROL;
2611 break;
2612
2613#ifdef __WXOSX__
2614 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2615 case WXK_RAW_CONTROL:
2616 m_modkeys |= wxMOD_RAW_CONTROL;
2617 break;
2618#endif
2619
2620 case WXK_LEFT:
2621 if (m_modkeys == wxMOD_CONTROL)
2622 parent_frame->DoStackDown(this);
2623 else if (g_bsmoothpanzoom) {
2624 StartTimedMovement();
2625 m_panx = -1;
2626 } else {
2627 PanCanvas(-panspeed, 0);
2628 }
2629 b_handled = true;
2630 break;
2631
2632 case WXK_UP:
2633 if (g_bsmoothpanzoom) {
2634 StartTimedMovement();
2635 m_pany = -1;
2636 } else
2637 PanCanvas(0, -panspeed);
2638 b_handled = true;
2639 break;
2640
2641 case WXK_RIGHT:
2642 if (m_modkeys == wxMOD_CONTROL)
2643 parent_frame->DoStackUp(this);
2644 else if (g_bsmoothpanzoom) {
2645 StartTimedMovement();
2646 m_panx = 1;
2647 } else
2648 PanCanvas(panspeed, 0);
2649 b_handled = true;
2650
2651 break;
2652
2653 case WXK_DOWN:
2654 if (g_bsmoothpanzoom) {
2655 StartTimedMovement();
2656 m_pany = 1;
2657 } else
2658 PanCanvas(0, panspeed);
2659 b_handled = true;
2660 break;
2661
2662 case WXK_F2:
2663 TogglebFollow();
2664 break;
2665
2666 case WXK_F3: {
2667 SetShowENCText(!GetShowENCText());
2668 Refresh(true);
2669 InvalidateGL();
2670 break;
2671 }
2672 case WXK_F4:
2673 if (!m_bMeasure_Active) {
2674 if (event.ShiftDown())
2675 m_bMeasure_DistCircle = true;
2676 else
2677 m_bMeasure_DistCircle = false;
2678
2679 StartMeasureRoute();
2680 } else {
2681 CancelMeasureRoute();
2682
2683 SetCursor(*pCursorArrow);
2684
2685 // SurfaceToolbar();
2686 InvalidateGL();
2687 Refresh(false);
2688 }
2689
2690 break;
2691
2692 case WXK_F5:
2693 parent_frame->ToggleColorScheme();
2694 gFrame->Raise();
2695 TriggerDeferredFocus();
2696 break;
2697
2698 case WXK_F6: {
2699 int mod = m_modkeys & wxMOD_SHIFT;
2700 if (mod != m_brightmod) {
2701 m_brightmod = mod;
2702 m_bbrightdir = !m_bbrightdir;
2703 }
2704
2705 if (!m_bbrightdir) {
2706 g_nbrightness -= 10;
2707 if (g_nbrightness <= MIN_BRIGHT) {
2708 g_nbrightness = MIN_BRIGHT;
2709 m_bbrightdir = true;
2710 }
2711 } else {
2712 g_nbrightness += 10;
2713 if (g_nbrightness >= MAX_BRIGHT) {
2714 g_nbrightness = MAX_BRIGHT;
2715 m_bbrightdir = false;
2716 }
2717 }
2718
2719 SetScreenBrightness(g_nbrightness);
2720 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2721
2722 SetFocus(); // just in case the external program steals it....
2723 gFrame->Raise(); // And reactivate the application main
2724
2725 break;
2726 }
2727
2728 case WXK_F7:
2729 parent_frame->DoStackDown(this);
2730 break;
2731
2732 case WXK_F8:
2733 parent_frame->DoStackUp(this);
2734 break;
2735
2736#ifndef __WXOSX__
2737 case WXK_F9: {
2738 ToggleCanvasQuiltMode();
2739 break;
2740 }
2741#endif
2742
2743 case WXK_F11:
2744 parent_frame->ToggleFullScreen();
2745 b_handled = true;
2746 break;
2747
2748 case WXK_F12: {
2749 if (m_modkeys == wxMOD_ALT) {
2750 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2751 } else {
2752 ToggleChartOutlines();
2753 }
2754 break;
2755 }
2756
2757 case WXK_PAUSE: // Drop MOB
2758 parent_frame->ActivateMOB();
2759 break;
2760
2761 // NUMERIC PAD
2762 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2763 case WXK_PAGEUP: {
2764 ZoomCanvas(g_plus_minus_zoom_factor, false);
2765 break;
2766 }
2767 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2768 case WXK_PAGEDOWN: {
2769 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2770 break;
2771 }
2772 case WXK_DELETE:
2773 case WXK_BACK:
2774 if (m_bMeasure_Active) {
2775 if (m_nMeasureState > 2) {
2776 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2777 m_pMeasureRoute->m_lastMousePointIndex =
2778 m_pMeasureRoute->GetnPoints();
2779 m_nMeasureState--;
2780 gFrame->RefreshAllCanvas();
2781 } else {
2782 CancelMeasureRoute();
2783 StartMeasureRoute();
2784 }
2785 }
2786 break;
2787 default:
2788 break;
2789 }
2790
2791 if (event.GetKeyCode() < 128) // ascii
2792 {
2793 int key_char = event.GetKeyCode();
2794
2795 // Handle both QWERTY and AZERTY keyboard separately for a few control
2796 // codes
2797 if (!g_b_assume_azerty) {
2798#ifdef __WXMAC__
2799 if (g_benable_rotate) {
2800 switch (key_char) {
2801 // On other platforms these are handled in OnKeyChar, which
2802 // (apparently) works better in some locales. On OS X it is better
2803 // to handle them here, since pressing Alt (which should change the
2804 // rotation speed) changes the key char and so prevents the keys
2805 // from working.
2806 case ']':
2807 RotateCanvas(1);
2808 b_handled = true;
2809 break;
2810
2811 case '[':
2812 RotateCanvas(-1);
2813 b_handled = true;
2814 break;
2815
2816 case '\\':
2817 DoRotateCanvas(0);
2818 b_handled = true;
2819 break;
2820 }
2821 }
2822#endif
2823 } else { // AZERTY
2824 switch (key_char) {
2825 case 43:
2826 ZoomCanvas(g_plus_minus_zoom_factor, false);
2827 break;
2828
2829 case 54: // '-' alpha/num pad
2830 // case 56: // '_' alpha/num pad
2831 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2832 break;
2833 }
2834 }
2835
2836#ifdef __WXOSX__
2837 // Ctrl+Cmd+F toggles fullscreen on macOS
2838 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
2839 m_modkeys & wxMOD_RAW_CONTROL) {
2840 parent_frame->ToggleFullScreen();
2841 return;
2842 }
2843#endif
2844
2845 if (event.ControlDown()) key_char -= 64;
2846
2847 if (key_char >= '0' && key_char <= '9')
2848 SetGroupIndex(key_char - '0');
2849 else
2850
2851 switch (key_char) {
2852 case 'A':
2853 SetShowENCAnchor(!GetShowENCAnchor());
2854 ReloadVP();
2855
2856 break;
2857
2858 case 'C':
2859 parent_frame->ToggleColorScheme();
2860 break;
2861
2862 case 'D': {
2863 int x, y;
2864 event.GetPosition(&x, &y);
2865 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
2866 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
2867 // First find out what kind of chart is being used
2868 if (!pPopupDetailSlider) {
2869 if (VPoint.b_quilt) {
2870 if (m_pQuilt) {
2871 if (m_pQuilt->GetChartAtPix(
2872 VPoint,
2873 wxPoint(
2874 x, y))) // = null if no chart loaded for this point
2875 {
2876 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2877 ->GetChartType();
2878 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2879 ->GetChartFamily();
2880 }
2881 }
2882 } else {
2883 if (m_singleChart) {
2884 ChartType = m_singleChart->GetChartType();
2885 ChartFam = m_singleChart->GetChartFamily();
2886 }
2887 }
2888 // If a charttype is found show the popupslider
2889 if ((ChartType != CHART_TYPE_UNKNOWN) ||
2890 (ChartFam != CHART_FAMILY_UNKNOWN)) {
2891 pPopupDetailSlider = new PopUpDSlide(
2892 this, -1, ChartType, ChartFam,
2893 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
2894 wxDefaultSize, wxSIMPLE_BORDER, "");
2895 if (pPopupDetailSlider) pPopupDetailSlider->Show();
2896 }
2897 } else //( !pPopupDetailSlider ) close popupslider
2898 {
2899 if (pPopupDetailSlider) pPopupDetailSlider->Close();
2900 pPopupDetailSlider = NULL;
2901 }
2902 break;
2903 }
2904
2905 case 'E':
2906 m_nmea_log->Show();
2907 m_nmea_log->Raise();
2908 break;
2909
2910 case 'L':
2911 SetShowENCLights(!GetShowENCLights());
2912 ReloadVP();
2913
2914 break;
2915
2916 case 'M':
2917 if (event.ShiftDown())
2918 m_bMeasure_DistCircle = true;
2919 else
2920 m_bMeasure_DistCircle = false;
2921
2922 StartMeasureRoute();
2923 break;
2924
2925 case 'N':
2926 if (g_bInlandEcdis && ps52plib) {
2927 SetENCDisplayCategory((_DisCat)STANDARD);
2928 }
2929 break;
2930
2931 case 'O':
2932 ToggleChartOutlines();
2933 break;
2934
2935 case 'Q':
2936 ToggleCanvasQuiltMode();
2937 break;
2938
2939 case 'P':
2940 parent_frame->ToggleTestPause();
2941 break;
2942 case 'R':
2943 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
2944 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
2945 g_iNavAidRadarRingsNumberVisible = 1;
2946 else if (!g_bNavAidRadarRingsShown &&
2947 g_iNavAidRadarRingsNumberVisible == 1)
2948 g_iNavAidRadarRingsNumberVisible = 0;
2949 break;
2950 case 'S':
2951 SetShowENCDepth(!m_encShowDepth);
2952 ReloadVP();
2953 break;
2954
2955 case 'T':
2956 SetShowENCText(!GetShowENCText());
2957 ReloadVP();
2958 break;
2959
2960 case 'U':
2961 SetShowENCDataQual(!GetShowENCDataQual());
2962 ReloadVP();
2963 break;
2964
2965 case 'V':
2966 m_bShowNavobjects = !m_bShowNavobjects;
2967 Refresh(true);
2968 break;
2969
2970 case 'W': // W Toggle CPA alarm
2971 ToggleCPAWarn();
2972
2973 break;
2974
2975 case 1: // Ctrl A
2976 TogglebFollow();
2977
2978 break;
2979
2980 case 2: // Ctrl B
2981 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
2982 break;
2983
2984 case 13: // Ctrl M // Drop Marker at cursor
2985 {
2986 if (event.ControlDown()) gFrame->DropMarker(false);
2987 break;
2988 }
2989
2990 case 14: // Ctrl N - Activate next waypoint in a route
2991 {
2992 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
2993 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
2994 if ((indexActive + 1) <= r->GetnPoints()) {
2995 g_pRouteMan->ActivateNextPoint(r, true);
2996 InvalidateGL();
2997 Refresh(false);
2998 }
2999 }
3000 break;
3001 }
3002
3003 case 15: // Ctrl O - Drop Marker at boat's position
3004 {
3005 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3006 break;
3007 }
3008
3009 case 32: // Special needs use space bar
3010 {
3011 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3012 break;
3013 }
3014
3015 case -32: // Ctrl Space // Drop MOB
3016 {
3017 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3018
3019 break;
3020 }
3021
3022 case -20: // Ctrl ,
3023 {
3024 parent_frame->DoSettings();
3025 break;
3026 }
3027 case 17: // Ctrl Q
3028 parent_frame->Close();
3029 return;
3030
3031 case 18: // Ctrl R
3032 StartRoute();
3033 return;
3034
3035 case 20: // Ctrl T
3036 if (NULL == pGoToPositionDialog) // There is one global instance of
3037 // the Go To Position Dialog
3038 pGoToPositionDialog = new GoToPositionDialog(this);
3039 pGoToPositionDialog->SetCanvas(this);
3040 pGoToPositionDialog->Show();
3041 break;
3042
3043 case 25: // Ctrl Y
3044 if (undo->AnythingToRedo()) {
3045 undo->RedoNextAction();
3046 InvalidateGL();
3047 Refresh(false);
3048 }
3049 break;
3050
3051 case 26:
3052 if (event.ShiftDown()) { // Shift-Ctrl-Z
3053 if (undo->AnythingToRedo()) {
3054 undo->RedoNextAction();
3055 InvalidateGL();
3056 Refresh(false);
3057 }
3058 } else { // Ctrl Z
3059 if (undo->AnythingToUndo()) {
3060 undo->UndoLastAction();
3061 InvalidateGL();
3062 Refresh(false);
3063 }
3064 }
3065 break;
3066
3067 case 27:
3068 // Generic break
3069 if (m_bMeasure_Active) {
3070 CancelMeasureRoute();
3071
3072 SetCursor(*pCursorArrow);
3073
3074 // SurfaceToolbar();
3075 gFrame->RefreshAllCanvas();
3076 }
3077
3078 if (m_routeState) // creating route?
3079 {
3080 FinishRoute();
3081 // SurfaceToolbar();
3082 InvalidateGL();
3083 Refresh(false);
3084 }
3085
3086 break;
3087
3088 case 7: // Ctrl G
3089 switch (gamma_state) {
3090 case (0):
3091 r_gamma_mult = 0;
3092 g_gamma_mult = 1;
3093 b_gamma_mult = 0;
3094 gamma_state = 1;
3095 break;
3096 case (1):
3097 r_gamma_mult = 1;
3098 g_gamma_mult = 0;
3099 b_gamma_mult = 0;
3100 gamma_state = 2;
3101 break;
3102 case (2):
3103 r_gamma_mult = 1;
3104 g_gamma_mult = 1;
3105 b_gamma_mult = 1;
3106 gamma_state = 0;
3107 break;
3108 }
3109 SetScreenBrightness(g_nbrightness);
3110
3111 break;
3112
3113 case 9: // Ctrl I
3114 if (event.ControlDown()) {
3115 m_bShowCompassWin = !m_bShowCompassWin;
3116 SetShowGPSCompassWindow(m_bShowCompassWin);
3117 Refresh(false);
3118 }
3119 break;
3120
3121 default:
3122 break;
3123
3124 } // switch
3125 }
3126
3127 // Allow OnKeyChar to catch the key events too.
3128 if (!b_handled) {
3129 event.Skip();
3130 }
3131}
3132
3133void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3134 if (SendKeyEventToPlugins(event))
3135 return; // PlugIn did something, and does not want the canvas to do
3136 // anything else
3137
3138 switch (event.GetKeyCode()) {
3139 case WXK_TAB:
3140 parent_frame->SwitchKBFocus(this);
3141 break;
3142
3143 case WXK_LEFT:
3144 case WXK_RIGHT:
3145 m_panx = 0;
3146 if (!m_pany) m_panspeed = 0;
3147 break;
3148
3149 case WXK_UP:
3150 case WXK_DOWN:
3151 m_pany = 0;
3152 if (!m_panx) m_panspeed = 0;
3153 break;
3154
3155 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3156 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3157 case WXK_PAGEUP:
3158 case WXK_PAGEDOWN:
3159 if (m_mustmove) DoMovement(m_mustmove);
3160
3161 m_zoom_factor = 1;
3162 break;
3163
3164 case WXK_ALT:
3165 m_modkeys &= ~wxMOD_ALT;
3166#ifdef OCPN_ALT_MENUBAR
3167#ifndef __WXOSX__
3168 // If the permanent menu bar is disabled, and we are not in the middle of
3169 // another key combo, then show the menu bar temporarily when Alt is
3170 // released (or hide it if already visible).
3171 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3172 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3173 parent_frame->ApplyGlobalSettings(false);
3174 }
3175 m_bMayToggleMenuBar = true;
3176#endif
3177#endif
3178 break;
3179
3180 case WXK_CONTROL:
3181 m_modkeys &= ~wxMOD_CONTROL;
3182 break;
3183 }
3184
3185 if (event.GetKeyCode() < 128) // ascii
3186 {
3187 int key_char = event.GetKeyCode();
3188
3189 // Handle both QWERTY and AZERTY keyboard separately for a few control
3190 // codes
3191 if (!g_b_assume_azerty) {
3192 switch (key_char) {
3193 case '+':
3194 case '=':
3195 case '-':
3196 case '_':
3197 case 54:
3198 case 56: // '_' alpha/num pad
3199 DoMovement(m_mustmove);
3200
3201 // m_zoom_factor = 1;
3202 break;
3203 case '[':
3204 case ']':
3205 DoMovement(m_mustmove);
3206 m_rotation_speed = 0;
3207 break;
3208 }
3209 } else {
3210 switch (key_char) {
3211 case 43:
3212 case 54: // '-' alpha/num pad
3213 case 56: // '_' alpha/num pad
3214 DoMovement(m_mustmove);
3215
3216 m_zoom_factor = 1;
3217 break;
3218 }
3219 }
3220 }
3221 event.Skip();
3222}
3223
3224void ChartCanvas::ToggleChartOutlines(void) {
3225 m_bShowOutlines = !m_bShowOutlines;
3226
3227 Refresh(false);
3228
3229#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3230 // needs a full refresh
3231 if (g_bopengl) InvalidateGL();
3232#endif
3233}
3234
3235void ChartCanvas::ToggleLookahead() {
3236 m_bLookAhead = !m_bLookAhead;
3237 m_OSoffsetx = 0; // center ownship
3238 m_OSoffsety = 0;
3239}
3240
3241void ChartCanvas::SetUpMode(int mode) {
3242 m_upMode = mode;
3243
3244 if (mode != NORTH_UP_MODE) {
3245 // Stuff the COGAvg table in case COGUp is selected
3246 double stuff = 0;
3247 if (!std::isnan(gCog)) stuff = gCog;
3248
3249 if (g_COGAvgSec > 0) {
3250 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3251 }
3252 g_COGAvg = stuff;
3253 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3254 } else {
3255 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3256 SetVPRotation(GetVPSkew());
3257 else
3258 SetVPRotation(0); /* reset to north up */
3259 }
3260
3261 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3262 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3263
3264 UpdateGPSCompassStatusBox(true);
3265 gFrame->DoChartUpdate();
3266}
3267
3268bool ChartCanvas::DoCanvasCOGSet(void) {
3269 if (GetUpMode() == NORTH_UP_MODE) return false;
3270 double cog_use = g_COGAvg;
3271 if (g_btenhertz) cog_use = gCog;
3272
3273 double rotation = 0;
3274 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3275 rotation = -gHdt * PI / 180.;
3276 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3277 rotation = -cog_use * PI / 180.;
3278
3279 SetVPRotation(rotation);
3280 return true;
3281}
3282
3283double easeOutCubic(double t) {
3284 // Starts quickly and slows down toward the end
3285 return 1.0 - pow(1.0 - t, 3.0);
3286}
3287
3288void ChartCanvas::StartChartDragInertia() {
3289 m_bChartDragging = false;
3290
3291 // Set some parameters
3292 m_chart_drag_inertia_time = 750; // msec
3293 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3294 m_last_elapsed = 0;
3295
3296 // Calculate ending drag velocity
3297 size_t n_vel = 10;
3298 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3299 int xacc = 0;
3300 int yacc = 0;
3301 double tacc = 0;
3302 size_t length = m_drag_vec_t.size();
3303 for (size_t i = 0; i < n_vel; i++) {
3304 xacc += m_drag_vec_x.at(length - 1 - i);
3305 yacc += m_drag_vec_y.at(length - 1 - i);
3306 tacc += m_drag_vec_t.at(length - 1 - i);
3307 }
3308 m_chart_drag_velocity_x = xacc / tacc;
3309 m_chart_drag_velocity_y = yacc / tacc;
3310
3311 m_chart_drag_inertia_active = true;
3312 // First callback as fast as possible.
3313 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3314}
3315
3316void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3317 if (!m_chart_drag_inertia_active) return;
3318
3319 // Calculate time fraction from 0..1
3320 wxLongLong now = wxGetLocalTimeMillis();
3321 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3322 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3323 if (t > 1.0) t = 1.0;
3324 double e = 1.0 - easeOutCubic(t); // 0..1
3325
3326 double dx =
3327 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3328 double dy =
3329 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3330
3331 m_last_elapsed = elapsed;
3332
3333 // Ensure that target destination lies on whole-pixel boundary
3334 // This allows the render engine to use a faster FBO copy method for drawing
3335 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3336 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3337 double inertia_lat, inertia_lon;
3338 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3339 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3340 // Check if ownship has moved off-screen
3341 if (!IsOwnshipOnScreen()) {
3342 m_bFollow = false; // update the follow flag
3343 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3344 UpdateFollowButtonState();
3345 m_OSoffsetx = 0;
3346 m_OSoffsety = 0;
3347 } else {
3348 m_OSoffsetx += dx;
3349 m_OSoffsety -= dy;
3350 }
3351
3352 Refresh(false);
3353
3354 // Stop condition
3355 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3356 m_chart_drag_inertia_timer.Stop();
3357
3358 // Disable chart pan movement logic
3359 m_target_lat = GetVP().clat;
3360 m_target_lon = GetVP().clon;
3361 m_pan_drag.x = m_pan_drag.y = 0;
3362 m_panx = m_pany = 0;
3363 m_chart_drag_inertia_active = false;
3364 DoCanvasUpdate();
3365
3366 } else {
3367 int target_redraw_interval = 40; // msec
3368 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3369 }
3370}
3371
3372void ChartCanvas::StopMovement() {
3373 m_panx = m_pany = 0;
3374 m_panspeed = 0;
3375 m_zoom_factor = 1;
3376 m_rotation_speed = 0;
3377 m_mustmove = 0;
3378#if 0
3379#if !defined(__WXGTK__) && !defined(__WXQT__)
3380 SetFocus();
3381 gFrame->Raise();
3382#endif
3383#endif
3384}
3385
3386/* instead of integrating in timer callbacks
3387 (which do not always get called fast enough)
3388 we can perform the integration of movement
3389 at each render frame based on the time change */
3390bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3391 // Start/restart the stop movement timer
3392 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3393
3394 if (!pMovementTimer->IsRunning()) {
3395 // printf("timer not running, starting\n");
3396 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3397 }
3398
3399 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3400 // already moving, gets called again because of key-repeat event
3401 return false;
3402 }
3403
3404 m_last_movement_time = wxDateTime::UNow();
3405
3406 return true;
3407}
3408void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3409 int nstep) {
3410 // Save the target
3411 m_target_lat = target_lat;
3412 m_target_lon = target_lon;
3413
3414 // Save the start point
3415 m_start_lat = GetVP().clat;
3416 m_start_lon = GetVP().clon;
3417
3418 m_VPMovementTimer.Start(1, true); // oneshot
3419 m_timed_move_vp_active = true;
3420 m_stvpc = 0;
3421 m_timedVP_step = nstep;
3422}
3423
3424void ChartCanvas::DoTimedMovementVP() {
3425 if (!m_timed_move_vp_active) return; // not active
3426 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3427 StopMovement();
3428 return;
3429 }
3430 // Stop condition
3431 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3432 double d2 =
3433 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3434 d2 = pow(d2, 0.5);
3435
3436 if (d2 < one_pix) {
3437 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3438 StopMovementVP();
3439 return;
3440 }
3441
3442 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3443 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3444 // StopMovementVP();
3445 // return;
3446 // }
3447
3448 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3449 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3450
3451 m_run_lat = new_lat;
3452 m_run_lon = new_lon;
3453
3454 // printf(" Timed\n");
3455 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3456}
3457
3458void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3459
3460void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3461
3462void ChartCanvas::StartTimedMovementTarget() {}
3463
3464void ChartCanvas::DoTimedMovementTarget() {}
3465
3466void ChartCanvas::StopMovementTarget() {}
3467
3468void ChartCanvas::DoTimedMovement() {
3469 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3470 !m_rotation_speed)
3471 return; /* not moving */
3472
3473 wxDateTime now = wxDateTime::UNow();
3474 long dt = 0;
3475 if (m_last_movement_time.IsValid())
3476 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3477
3478 m_last_movement_time = now;
3479
3480 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3481 dt = 500;
3482
3483 DoMovement(dt);
3484}
3485
3487 /* if we get here quickly assume 1ms so that some movement occurs */
3488 if (dt == 0) dt = 1;
3489
3490 m_mustmove -= dt;
3491 if (m_mustmove < 0) m_mustmove = 0;
3492
3493 if (m_pan_drag.x || m_pan_drag.y) {
3494 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3495 m_pan_drag.x = m_pan_drag.y = 0;
3496 }
3497
3498 if (m_panx || m_pany) {
3499 const double slowpan = .1, maxpan = 2;
3500 if (m_modkeys == wxMOD_ALT)
3501 m_panspeed = slowpan;
3502 else {
3503 m_panspeed += (double)dt / 500; /* apply acceleration */
3504 m_panspeed = wxMin(maxpan, m_panspeed);
3505 }
3506 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3507 }
3508
3509 if (m_zoom_factor != 1) {
3510 double alpha = 400, beta = 1.5;
3511 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3512
3513 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3514
3515 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3516
3517 // Try to hit the zoom target exactly.
3518 // if(m_wheelzoom_stop_oneshot > 0)
3519 {
3520 if (zoom_factor > 1) {
3521 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3522 zoom_factor = VPoint.chart_scale / m_zoom_target;
3523 }
3524
3525 else if (zoom_factor < 1) {
3526 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3527 zoom_factor = VPoint.chart_scale / m_zoom_target;
3528 }
3529 }
3530
3531 if (fabs(zoom_factor - 1) > 1e-4) {
3532 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3533 } else {
3534 StopMovement();
3535 }
3536
3537 if (m_wheelzoom_stop_oneshot > 0) {
3538 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3539 m_wheelzoom_stop_oneshot = 0;
3540 StopMovement();
3541 }
3542
3543 // Don't overshoot the zoom target.
3544 if (zoom_factor > 1) {
3545 if (VPoint.chart_scale <= m_zoom_target) {
3546 m_wheelzoom_stop_oneshot = 0;
3547 StopMovement();
3548 }
3549 } else if (zoom_factor < 1) {
3550 if (VPoint.chart_scale >= m_zoom_target) {
3551 m_wheelzoom_stop_oneshot = 0;
3552 StopMovement();
3553 }
3554 }
3555 }
3556 }
3557
3558 if (m_rotation_speed) { /* in degrees per second */
3559 double speed = m_rotation_speed;
3560 if (m_modkeys == wxMOD_ALT) speed /= 10;
3561 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3562 }
3563}
3564
3565void ChartCanvas::SetColorScheme(ColorScheme cs) {
3566 SetAlertString("");
3567
3568 // Setup ownship image pointers
3569 switch (cs) {
3570 case GLOBAL_COLOR_SCHEME_DAY:
3571 m_pos_image_red = &m_os_image_red_day;
3572 m_pos_image_grey = &m_os_image_grey_day;
3573 m_pos_image_yellow = &m_os_image_yellow_day;
3574 m_pos_image_user = m_pos_image_user_day;
3575 m_pos_image_user_grey = m_pos_image_user_grey_day;
3576 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3577 m_cTideBitmap = m_bmTideDay;
3578 m_cCurrentBitmap = m_bmCurrentDay;
3579
3580 break;
3581 case GLOBAL_COLOR_SCHEME_DUSK:
3582 m_pos_image_red = &m_os_image_red_dusk;
3583 m_pos_image_grey = &m_os_image_grey_dusk;
3584 m_pos_image_yellow = &m_os_image_yellow_dusk;
3585 m_pos_image_user = m_pos_image_user_dusk;
3586 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3587 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3588 m_cTideBitmap = m_bmTideDusk;
3589 m_cCurrentBitmap = m_bmCurrentDusk;
3590 break;
3591 case GLOBAL_COLOR_SCHEME_NIGHT:
3592 m_pos_image_red = &m_os_image_red_night;
3593 m_pos_image_grey = &m_os_image_grey_night;
3594 m_pos_image_yellow = &m_os_image_yellow_night;
3595 m_pos_image_user = m_pos_image_user_night;
3596 m_pos_image_user_grey = m_pos_image_user_grey_night;
3597 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3598 m_cTideBitmap = m_bmTideNight;
3599 m_cCurrentBitmap = m_bmCurrentNight;
3600 break;
3601 default:
3602 m_pos_image_red = &m_os_image_red_day;
3603 m_pos_image_grey = &m_os_image_grey_day;
3604 m_pos_image_yellow = &m_os_image_yellow_day;
3605 m_pos_image_user = m_pos_image_user_day;
3606 m_pos_image_user_grey = m_pos_image_user_grey_day;
3607 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3608 m_cTideBitmap = m_bmTideDay;
3609 m_cCurrentBitmap = m_bmCurrentDay;
3610 break;
3611 }
3612
3613 CreateDepthUnitEmbossMaps(cs);
3614 CreateOZEmbossMapData(cs);
3615
3616 // Set up fog effect base color
3617 m_fog_color = wxColor(
3618 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3619 float dim = 1.0;
3620 switch (cs) {
3621 case GLOBAL_COLOR_SCHEME_DUSK:
3622 dim = 0.5;
3623 break;
3624 case GLOBAL_COLOR_SCHEME_NIGHT:
3625 dim = 0.25;
3626 break;
3627 default:
3628 break;
3629 }
3630 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3631 m_fog_color.Blue() * dim);
3632
3633 // Really dark
3634#if 0
3635 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3636 SetBackgroundColour( wxColour(0,0,0) );
3637
3638 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3639 }
3640 else{
3641 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3642#ifndef __WXMAC__
3643 SetBackgroundColour( wxNullColour );
3644#endif
3645 }
3646#endif
3647
3648 // UpdateToolbarColorScheme(cs);
3649
3650 m_Piano->SetColorScheme(cs);
3651
3652 m_Compass->SetColorScheme(cs);
3653
3654 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3655
3656 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3657
3658 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3659 if (m_notification_button) {
3660 m_notification_button->SetColorScheme(cs);
3661 }
3662
3663#ifdef ocpnUSE_GL
3664 if (g_bopengl && m_glcc) {
3665 m_glcc->SetColorScheme(cs);
3666 g_glTextureManager->ClearAllRasterTextures();
3667 // m_glcc->FlushFBO();
3668 }
3669#endif
3670 SetbTCUpdate(true); // force re-render of tide/current locators
3671 m_brepaint_piano = true;
3672
3673 ReloadVP();
3674
3675 m_cs = cs;
3676}
3677
3678wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3679 wxImage img = Bitmap.ConvertToImage();
3680 int sx = img.GetWidth();
3681 int sy = img.GetHeight();
3682
3683 wxImage new_img(img);
3684
3685 for (int i = 0; i < sx; i++) {
3686 for (int j = 0; j < sy; j++) {
3687 if (!img.IsTransparent(i, j)) {
3688 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3689 (unsigned char)(img.GetGreen(i, j) * factor),
3690 (unsigned char)(img.GetBlue(i, j) * factor));
3691 }
3692 }
3693 }
3694
3695 wxBitmap ret = wxBitmap(new_img);
3696
3697 return ret;
3698}
3699
3700void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3701 int max) {
3702 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3703 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3704
3705 if (!m_pBrightPopup) {
3706 // Calculate size
3707 int x, y;
3708 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3709
3710 m_pBrightPopup = new TimedPopupWin(this, 3);
3711
3712 m_pBrightPopup->SetSize(x, y);
3713 m_pBrightPopup->Move(120, 120);
3714 }
3715
3716 int bmpsx = m_pBrightPopup->GetSize().x;
3717 int bmpsy = m_pBrightPopup->GetSize().y;
3718
3719 wxBitmap bmp(bmpsx, bmpsx);
3720 wxMemoryDC mdc(bmp);
3721
3722 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3723 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3724 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3725 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3726 mdc.Clear();
3727
3728 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3729
3730 mdc.SetFont(*pfont);
3731 wxString val;
3732
3733 if (brightness == max)
3734 val = "MAX";
3735 else if (brightness == min)
3736 val = "MIN";
3737 else
3738 val.Printf("%3d", brightness);
3739
3740 mdc.DrawText(val, 0, 0);
3741
3742 mdc.SelectObject(wxNullBitmap);
3743
3744 m_pBrightPopup->SetBitmap(bmp);
3745 m_pBrightPopup->Show();
3746 m_pBrightPopup->Refresh();
3747}
3748
3749void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3750 m_b_rot_hidef = true;
3751 ReloadVP();
3752}
3753
3754void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3755 if (!g_bRollover) return;
3756
3757 bool b_need_refresh = false;
3758
3759 wxSize win_size = GetSize() * m_displayScale;
3760 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3761
3762 // Handle the AIS Rollover Window first
3763 bool showAISRollover = false;
3764 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3765 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3766 SelectItem *pFind = pSelectAIS->FindSelection(
3767 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3768 if (pFind) {
3769 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3770 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3771
3772 if (ptarget) {
3773 showAISRollover = true;
3774
3775 if (NULL == m_pAISRolloverWin) {
3776 m_pAISRolloverWin = new RolloverWin(this);
3777 m_pAISRolloverWin->IsActive(false);
3778 b_need_refresh = true;
3779 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3780 m_AISRollover_MMSI != FoundAIS_MMSI) {
3781 // Sometimes the mouse moves fast enough to get over a new AIS
3782 // target before the one-shot has fired to remove the old target.
3783 // Result: wrong target data is shown.
3784 // Detect this case,close the existing rollover ASAP, and restart
3785 // the timer.
3786 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3787 m_pAISRolloverWin->IsActive(false);
3788 m_AISRollover_MMSI = 0;
3789 Refresh();
3790 return;
3791 }
3792
3793 m_AISRollover_MMSI = FoundAIS_MMSI;
3794
3795 if (!m_pAISRolloverWin->IsActive()) {
3796 wxString s = ptarget->GetRolloverString();
3797 m_pAISRolloverWin->SetString(s);
3798
3799 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3800 AIS_ROLLOVER, win_size);
3801 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3802 m_pAISRolloverWin->IsActive(true);
3803 b_need_refresh = true;
3804 }
3805 }
3806 } else {
3807 m_AISRollover_MMSI = 0;
3808 showAISRollover = false;
3809 }
3810 }
3811
3812 // Maybe turn the rollover off
3813 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3814 m_pAISRolloverWin->IsActive(false);
3815 m_AISRollover_MMSI = 0;
3816 b_need_refresh = true;
3817 }
3818
3819 // Now the Route info rollover
3820 // Show the route segment info
3821 bool showRouteRollover = false;
3822
3823 if (NULL == m_pRolloverRouteSeg) {
3824 // Get a list of all selectable sgements, and search for the first
3825 // visible segment as the rollover target.
3826
3827 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3828 SelectableItemList SelList = pSelect->FindSelectionList(
3829 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3830 wxSelectableItemListNode *node = SelList.GetFirst();
3831 while (node) {
3832 SelectItem *pFindSel = node->GetData();
3833
3834 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3835
3836 if (pr && pr->IsVisible()) {
3837 m_pRolloverRouteSeg = pFindSel;
3838 showRouteRollover = true;
3839
3840 if (NULL == m_pRouteRolloverWin) {
3841 m_pRouteRolloverWin = new RolloverWin(this, 10);
3842 m_pRouteRolloverWin->IsActive(false);
3843 }
3844
3845 if (!m_pRouteRolloverWin->IsActive()) {
3846 wxString s;
3847 RoutePoint *segShow_point_a =
3848 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
3849 RoutePoint *segShow_point_b =
3850 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
3851
3852 double brg, dist;
3853 DistanceBearingMercator(
3854 segShow_point_b->m_lat, segShow_point_b->m_lon,
3855 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
3856
3857 if (!pr->m_bIsInLayer)
3858 s.Append(_("Route") + ": ");
3859 else
3860 s.Append(_("Layer Route: "));
3861
3862 if (pr->m_RouteNameString.IsEmpty())
3863 s.Append(_("(unnamed)"));
3864 else
3865 s.Append(pr->m_RouteNameString);
3866
3867 s << "\n"
3868 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
3869 << "\n"
3870 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
3871 << segShow_point_b->GetName() << "\n";
3872
3873 if (g_bShowTrue)
3874 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
3875 (int)floor(brg + 0.5), 0x00B0);
3876 if (g_bShowMag) {
3877 double latAverage =
3878 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
3879 double lonAverage =
3880 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
3881 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
3882
3883 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
3884 (int)floor(varBrg + 0.5), 0x00B0);
3885 }
3886
3887 s << FormatDistanceAdaptive(dist);
3888
3889 // Compute and display cumulative distance from route start point to
3890 // current leg end point and RNG,TTG,ETA from ship to current leg end
3891 // point for active route
3892 double shiptoEndLeg = 0.;
3893 bool validActive = false;
3894 if (pr->IsActive() &&
3895 pr->pRoutePointList->GetFirst()->GetData()->m_bIsActive)
3896 validActive = true;
3897
3898 if (segShow_point_a != pr->pRoutePointList->GetFirst()->GetData()) {
3899 wxRoutePointListNode *node =
3900 (pr->pRoutePointList)->GetFirst()->GetNext();
3901 RoutePoint *prp;
3902 float dist_to_endleg = 0;
3903 wxString t;
3904
3905 while (node) {
3906 prp = node->GetData();
3907 if (validActive)
3908 shiptoEndLeg += prp->m_seg_len;
3909 else if (prp->m_bIsActive)
3910 validActive = true;
3911 dist_to_endleg += prp->m_seg_len;
3912 if (prp->IsSame(segShow_point_a)) break;
3913 node = node->GetNext();
3914 }
3915 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
3916 }
3917 // write from ship to end selected leg point data if the route is
3918 // active
3919 if (validActive) {
3920 s << "\n"
3921 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
3922 shiptoEndLeg +=
3923 g_pRouteMan
3924 ->GetCurrentRngToActivePoint(); // add distance from ship
3925 // to active point
3926 shiptoEndLeg +=
3927 segShow_point_b
3928 ->m_seg_len; // add the lenght of the selected leg
3929 s << FormatDistanceAdaptive(shiptoEndLeg);
3930 // ensure sog/cog are valid and vmg is positive to keep data
3931 // coherent
3932 double vmg = 0.;
3933 if (!std::isnan(gCog) && !std::isnan(gSog))
3934 vmg = gSog *
3935 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
3936 PI / 180.);
3937 if (vmg > 0.) {
3938 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
3939 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
3940 s << " - "
3941 << wxString(ttg_sec > SECONDS_PER_DAY
3942 ? ttg_span.Format(_("%Dd %H:%M"))
3943 : ttg_span.Format(_("%H:%M")));
3944 wxDateTime dtnow, eta;
3945 eta = dtnow.SetToCurrent().Add(ttg_span);
3946 s << " - " << eta.Format("%b").Mid(0, 4)
3947 << eta.Format(" %d %H:%M");
3948 } else
3949 s << " ---- ----";
3950 }
3951 m_pRouteRolloverWin->SetString(s);
3952
3953 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3954 LEG_ROLLOVER, win_size);
3955 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
3956 m_pRouteRolloverWin->IsActive(true);
3957 b_need_refresh = true;
3958 showRouteRollover = true;
3959 break;
3960 }
3961 } else
3962 node = node->GetNext();
3963 }
3964 } else {
3965 // Is the cursor still in select radius, and not timed out?
3966 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3967 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
3968 m_pRolloverRouteSeg))
3969 showRouteRollover = false;
3970 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
3971 showRouteRollover = false;
3972 else
3973 showRouteRollover = true;
3974 }
3975
3976 // If currently creating a route, do not show this rollover window
3977 if (m_routeState) showRouteRollover = false;
3978
3979 // Similar for AIS target rollover window
3980 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
3981 showRouteRollover = false;
3982
3983 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
3984 !showRouteRollover) {
3985 m_pRouteRolloverWin->IsActive(false);
3986 m_pRolloverRouteSeg = NULL;
3987 m_pRouteRolloverWin->Destroy();
3988 m_pRouteRolloverWin = NULL;
3989 b_need_refresh = true;
3990 } else if (m_pRouteRolloverWin && showRouteRollover) {
3991 m_pRouteRolloverWin->IsActive(true);
3992 b_need_refresh = true;
3993 }
3994
3995 // Now the Track info rollover
3996 // Show the track segment info
3997 bool showTrackRollover = false;
3998
3999 if (NULL == m_pRolloverTrackSeg) {
4000 // Get a list of all selectable sgements, and search for the first
4001 // visible segment as the rollover target.
4002
4003 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4004 SelectableItemList SelList = pSelect->FindSelectionList(
4005 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4006 wxSelectableItemListNode *node = SelList.GetFirst();
4007 while (node) {
4008 SelectItem *pFindSel = node->GetData();
4009
4010 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4011
4012 if (pt && pt->IsVisible()) {
4013 m_pRolloverTrackSeg = pFindSel;
4014 showTrackRollover = true;
4015
4016 if (NULL == m_pTrackRolloverWin) {
4017 m_pTrackRolloverWin = new RolloverWin(this, 10);
4018 m_pTrackRolloverWin->IsActive(false);
4019 }
4020
4021 if (!m_pTrackRolloverWin->IsActive()) {
4022 wxString s;
4023 TrackPoint *segShow_point_a =
4024 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4025 TrackPoint *segShow_point_b =
4026 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4027
4028 double brg, dist;
4029 DistanceBearingMercator(
4030 segShow_point_b->m_lat, segShow_point_b->m_lon,
4031 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4032
4033 if (!pt->m_bIsInLayer)
4034 s.Append(_("Track") + ": ");
4035 else
4036 s.Append(_("Layer Track: "));
4037
4038 if (pt->GetName().IsEmpty())
4039 s.Append(_("(unnamed)"));
4040 else
4041 s.Append(pt->GetName());
4042 double tlenght = pt->Length();
4043 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4044 if (pt->GetLastPoint()->GetTimeString() &&
4045 pt->GetPoint(0)->GetTimeString()) {
4046 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4047 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4048 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4049 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4050 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4051 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4052 << getUsrSpeedUnit();
4053 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4054 : ttime.Format(" %H:%M"));
4055 }
4056 }
4057
4058 if (g_bShowTrackPointTime &&
4059 strlen(segShow_point_b->GetTimeString())) {
4060 wxString stamp = segShow_point_b->GetTimeString();
4061 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4062 if (timestamp.IsValid()) {
4063 // Format track rollover timestamp to OCPN global TZ setting
4066 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4067 }
4068 s << "\n" << _("Segment Created: ") << stamp;
4069 }
4070
4071 s << "\n";
4072 if (g_bShowTrue)
4073 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4074 0x00B0);
4075
4076 if (g_bShowMag) {
4077 double latAverage =
4078 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4079 double lonAverage =
4080 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4081 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4082
4083 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4084 0x00B0);
4085 }
4086
4087 s << FormatDistanceAdaptive(dist);
4088
4089 if (segShow_point_a->GetTimeString() &&
4090 segShow_point_b->GetTimeString()) {
4091 wxDateTime apoint = segShow_point_a->GetCreateTime();
4092 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4093 if (apoint.IsValid() && bpoint.IsValid()) {
4094 double segmentSpeed = toUsrSpeed(
4095 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4096 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4097 << getUsrSpeedUnit();
4098 }
4099 }
4100
4101 m_pTrackRolloverWin->SetString(s);
4102
4103 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4104 LEG_ROLLOVER, win_size);
4105 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4106 m_pTrackRolloverWin->IsActive(true);
4107 b_need_refresh = true;
4108 showTrackRollover = true;
4109 break;
4110 }
4111 } else
4112 node = node->GetNext();
4113 }
4114 } else {
4115 // Is the cursor still in select radius, and not timed out?
4116 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4117 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4118 m_pRolloverTrackSeg))
4119 showTrackRollover = false;
4120 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4121 showTrackRollover = false;
4122 else
4123 showTrackRollover = true;
4124 }
4125
4126 // Similar for AIS target rollover window
4127 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4128 showTrackRollover = false;
4129
4130 // Similar for route rollover window
4131 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4132 showTrackRollover = false;
4133
4134 // TODO We onlt show tracks on primary canvas....
4135 // if(!IsPrimaryCanvas())
4136 // showTrackRollover = false;
4137
4138 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4139 !showTrackRollover) {
4140 m_pTrackRolloverWin->IsActive(false);
4141 m_pRolloverTrackSeg = NULL;
4142 m_pTrackRolloverWin->Destroy();
4143 m_pTrackRolloverWin = NULL;
4144 b_need_refresh = true;
4145 } else if (m_pTrackRolloverWin && showTrackRollover) {
4146 m_pTrackRolloverWin->IsActive(true);
4147 b_need_refresh = true;
4148 }
4149
4150 if (b_need_refresh) Refresh();
4151}
4152
4153void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4154 if ((GetShowENCLights() || m_bsectors_shown) &&
4155 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4156 extendedSectorLegs)) {
4157 if (!m_bsectors_shown) {
4158 ReloadVP(false);
4159 m_bsectors_shown = true;
4160 }
4161 } else {
4162 if (m_bsectors_shown) {
4163 ReloadVP(false);
4164 m_bsectors_shown = false;
4165 }
4166 }
4167
4168// This is here because GTK status window update is expensive..
4169// cairo using pango rebuilds the font every time so is very
4170// inefficient
4171// Anyway, only update the status bar when this timer expires
4172#if defined(__WXGTK__) || defined(__WXQT__)
4173 {
4174 // Check the absolute range of the cursor position
4175 // There could be a window wherein the chart geoereferencing is not
4176 // valid....
4177 double cursor_lat, cursor_lon;
4178 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4179
4180 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4181 while (cursor_lon < -180.) cursor_lon += 360.;
4182
4183 while (cursor_lon > 180.) cursor_lon -= 360.;
4184
4185 SetCursorStatus(cursor_lat, cursor_lon);
4186 }
4187 }
4188#endif
4189}
4190
4191void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4192 if (!parent_frame->m_pStatusBar) return;
4193
4194 wxString s1;
4195 s1 += " ";
4196 s1 += toSDMM(1, cursor_lat);
4197 s1 += " ";
4198 s1 += toSDMM(2, cursor_lon);
4199
4200 if (STAT_FIELD_CURSOR_LL >= 0)
4201 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4202
4203 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4204
4205 double brg, dist;
4206 wxString sm;
4207 wxString st;
4208 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4209 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4210 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4211
4212 wxString s = st + sm;
4213 s << FormatDistanceAdaptive(dist);
4214
4215 // CUSTOMIZATION - LIVE ETA OPTION
4216 // -------------------------------------------------------
4217 // Calculate an "live" ETA based on route starting from the current
4218 // position of the boat and goes to the cursor of the mouse.
4219 // In any case, an standard ETA will be calculated with a default speed
4220 // of the boat to give an estimation of the route (in particular if GPS
4221 // is off).
4222
4223 // Display only if option "live ETA" is selected in Settings > Display >
4224 // General.
4225 if (g_bShowLiveETA) {
4226 float realTimeETA;
4227 float boatSpeed;
4228 float boatSpeedDefault = g_defaultBoatSpeed;
4229
4230 // Calculate Estimate Time to Arrival (ETA) in minutes
4231 // Check before is value not closed to zero (it will make an very big
4232 // number...)
4233 if (!std::isnan(gSog)) {
4234 boatSpeed = gSog;
4235 if (boatSpeed < 0.5) {
4236 realTimeETA = 0;
4237 } else {
4238 realTimeETA = dist / boatSpeed * 60;
4239 }
4240 } else {
4241 realTimeETA = 0;
4242 }
4243
4244 // Add space after distance display
4245 s << " ";
4246 // Display ETA
4247 s << minutesToHoursDays(realTimeETA);
4248
4249 // In any case, display also an ETA with default speed at 6knts
4250
4251 s << " [@";
4252 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4253 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4254 s << " ";
4255 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4256 s << "]";
4257 }
4258 // END OF - LIVE ETA OPTION
4259
4260 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4261}
4262
4263// CUSTOMIZATION - FORMAT MINUTES
4264// -------------------------------------------------------
4265// New function to format minutes into a more readable format:
4266// * Hours + minutes, or
4267// * Days + hours.
4268wxString minutesToHoursDays(float timeInMinutes) {
4269 wxString s;
4270
4271 if (timeInMinutes == 0) {
4272 s << "--min";
4273 }
4274
4275 // Less than 60min, keep time in minutes
4276 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4277 s << wxString::Format("%d", (int)timeInMinutes);
4278 s << "min";
4279 }
4280
4281 // Between 1h and less than 24h, display time in hours, minutes
4282 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4283 int hours;
4284 int min;
4285 hours = (int)timeInMinutes / 60;
4286 min = (int)timeInMinutes % 60;
4287
4288 if (min == 0) {
4289 s << wxString::Format("%d", hours);
4290 s << "h";
4291 } else {
4292 s << wxString::Format("%d", hours);
4293 s << "h";
4294 s << wxString::Format("%d", min);
4295 s << "min";
4296 }
4297
4298 }
4299
4300 // More than 24h, display time in days, hours
4301 else if (timeInMinutes > 24 * 60) {
4302 int days;
4303 int hours;
4304 days = (int)(timeInMinutes / 60) / 24;
4305 hours = (int)(timeInMinutes / 60) % 24;
4306
4307 if (hours == 0) {
4308 s << wxString::Format("%d", days);
4309 s << "d";
4310 } else {
4311 s << wxString::Format("%d", days);
4312 s << "d";
4313 s << wxString::Format("%d", hours);
4314 s << "h";
4315 }
4316 }
4317
4318 return s;
4319}
4320
4321// END OF CUSTOMIZATION - FORMAT MINUTES
4322// Thanks open source code ;-)
4323// -------------------------------------------------------
4324
4325void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4326 double clat, clon;
4327 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4328 *lat = clat;
4329 *lon = clon;
4330}
4331
4332void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4333 wxPoint2DDouble *r) {
4334 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4335}
4336
4338 double rlon, wxPoint2DDouble *r) {
4339 // If the Current Chart is a raster chart, and the
4340 // requested lat/long is within the boundaries of the chart,
4341 // and the VP is not rotated,
4342 // then use the embedded BSB chart georeferencing algorithm
4343 // for greater accuracy
4344 // Additionally, use chart embedded georef if the projection is TMERC
4345 // i.e. NOT MERCATOR and NOT POLYCONIC
4346
4347 // If for some reason the chart rejects the request by returning an error,
4348 // then fall back to Viewport Projection estimate from canvas parameters
4349 if (!g_bopengl && m_singleChart &&
4350 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4351 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4352 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4353 (m_singleChart->GetChartProjectionType() !=
4354 PROJECTION_TRANSVERSE_MERCATOR) &&
4355 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4356 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4357 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4358 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4359 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4360 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4361 // Cur_BSB_Ch->GetCOVRTablenPoints
4362 // ( 0 ), rlon,
4363 // rlat );
4364 // bInside = true;
4365 // if ( bInside )
4366 if (Cur_BSB_Ch) {
4367 // This is a Raster chart....
4368 // If the VP is changing, the raster chart parameters may not yet be
4369 // setup So do that before accessing the chart's embedded
4370 // georeferencing
4371 Cur_BSB_Ch->SetVPRasterParms(vp);
4372 double rpixxd, rpixyd;
4373 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4374 r->m_x = rpixxd;
4375 r->m_y = rpixyd;
4376 return;
4377 }
4378 }
4379 }
4380
4381 // if needed, use the VPoint scaling estimator,
4382 *r = vp.GetDoublePixFromLL(rlat, rlon);
4383}
4384
4385// This routine might be deleted and all of the rendering improved
4386// to have floating point accuracy
4387bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4388 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4389}
4390
4391bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4392 wxPoint *r) {
4393 wxPoint2DDouble p;
4394 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4395
4396 // some projections give nan values when invisible values (other side of
4397 // world) are requested we should stop using integer coordinates or return
4398 // false here (and test it everywhere)
4399 if (std::isnan(p.m_x)) {
4400 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4401 return false;
4402 }
4403
4404 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4405 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4406 else
4407 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4408
4409 return true;
4410}
4411
4412void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4413 double &lon) {
4414 // If the Current Chart is a raster chart, and the
4415 // requested x,y is within the boundaries of the chart,
4416 // and the VP is not rotated,
4417 // then use the embedded BSB chart georeferencing algorithm
4418 // for greater accuracy
4419 // Additionally, use chart embedded georef if the projection is TMERC
4420 // i.e. NOT MERCATOR and NOT POLYCONIC
4421
4422 // If for some reason the chart rejects the request by returning an error,
4423 // then fall back to Viewport Projection estimate from canvas parameters
4424 bool bUseVP = true;
4425
4426 if (!g_bopengl && m_singleChart &&
4427 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4428 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4429 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4430 (m_singleChart->GetChartProjectionType() !=
4431 PROJECTION_TRANSVERSE_MERCATOR) &&
4432 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4433 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4434 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4435 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4436
4437 // TODO maybe need iterative process to validate bInside
4438 // first pass is mercator, then check chart boundaries
4439
4440 if (Cur_BSB_Ch) {
4441 // This is a Raster chart....
4442 // If the VP is changing, the raster chart parameters may not yet be
4443 // setup So do that before accessing the chart's embedded
4444 // georeferencing
4445 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4446
4447 double slat, slon;
4448 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4449 lat = slat;
4450
4451 if (slon < -180.)
4452 slon += 360.;
4453 else if (slon > 180.)
4454 slon -= 360.;
4455
4456 lon = slon;
4457 bUseVP = false;
4458 }
4459 }
4460 }
4461
4462 // if needed, use the VPoint scaling estimator
4463 if (bUseVP) {
4464 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4465 }
4466}
4467
4469 StopMovement();
4470 DoZoomCanvas(factor, false);
4471 extendedSectorLegs.clear();
4472}
4473
4474void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4475 bool stoptimer) {
4476 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4477
4478 if (g_bsmoothpanzoom) {
4479 if (StartTimedMovement(stoptimer)) {
4480 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4481 m_zoom_factor = factor;
4482 }
4483
4484 m_zoom_target = VPoint.chart_scale / factor;
4485 } else {
4486 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4487
4488 DoZoomCanvas(factor, can_zoom_to_cursor);
4489 }
4490
4491 extendedSectorLegs.clear();
4492}
4493
4494void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4495 // possible on startup
4496 if (!ChartData) return;
4497 if (!m_pCurrentStack) return;
4498
4499 /* TODO: queue the quilted loading code to a background thread
4500 so yield is never called from here, and also rendering is not delayed */
4501
4502 // Cannot allow Yield() re-entrancy here
4503 if (m_bzooming) return;
4504 m_bzooming = true;
4505
4506 double old_ppm = GetVP().view_scale_ppm;
4507
4508 // Capture current cursor position for zoom to cursor
4509 double zlat = m_cursor_lat;
4510 double zlon = m_cursor_lon;
4511
4512 double proposed_scale_onscreen =
4513 GetVP().chart_scale /
4514 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4515 bool b_do_zoom = false;
4516
4517 if (factor > 1) {
4518 b_do_zoom = true;
4519
4520 // double zoom_factor = factor;
4521
4522 ChartBase *pc = NULL;
4523
4524 if (!VPoint.b_quilt) {
4525 pc = m_singleChart;
4526 } else {
4527 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4528 if (new_db_index >= 0)
4529 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4530 else { // for whatever reason, no reference chart is known
4531 // Choose the smallest scale chart on the current stack
4532 // and then adjust for scale range
4533 int current_ref_stack_index = -1;
4534 if (m_pCurrentStack->nEntry) {
4535 int trial_index =
4536 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4537 m_pQuilt->SetReferenceChart(trial_index);
4538 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4539 if (new_db_index >= 0)
4540 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4541 }
4542 }
4543
4544 if (m_pCurrentStack)
4545 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4546 new_db_index); // highlite the correct bar entry
4547 }
4548
4549 if (pc) {
4550 // double target_scale_ppm = GetVPScale() * zoom_factor;
4551 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4552 // target_scale_ppm;
4553
4554 // Query the chart to determine the appropriate zoom range
4555 double min_allowed_scale =
4556 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4557
4558 if (proposed_scale_onscreen < min_allowed_scale) {
4559 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4560 m_zoom_factor = 1; /* stop zooming */
4561 b_do_zoom = false;
4562 } else
4563 proposed_scale_onscreen = min_allowed_scale;
4564 }
4565
4566 } else {
4567 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4568 }
4569
4570 } else if (factor < 1) {
4571 b_do_zoom = true;
4572
4573 ChartBase *pc = NULL;
4574
4575 bool b_smallest = false;
4576
4577 if (!VPoint.b_quilt) { // not quilted
4578 pc = m_singleChart;
4579
4580 if (pc) {
4581 // If m_singleChart is not on the screen, unbound the zoomout
4582 LLBBox viewbox = VPoint.GetBBox();
4583 // BoundingBox chart_box;
4584 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4585 double max_allowed_scale;
4586
4587 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4588
4589 // We can allow essentially unbounded zoomout in single chart mode
4590 // if( ChartData->GetDBBoundingBox( current_index,
4591 // &chart_box ) &&
4592 // !viewbox.IntersectOut( chart_box ) )
4593 // // Clamp the minimum scale zoom-out to the value
4594 // specified by the chart max_allowed_scale =
4595 // wxMin(max_allowed_scale, 4.0 *
4596 // pc->GetNormalScaleMax(
4597 // GetCanvasScaleFactor(),
4598 // GetCanvasWidth() ) );
4599 if (proposed_scale_onscreen > max_allowed_scale) {
4600 m_zoom_factor = 1; /* stop zooming */
4601 proposed_scale_onscreen = max_allowed_scale;
4602 }
4603 }
4604
4605 } else {
4606 int new_db_index = m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4607 if (new_db_index >= 0)
4608 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4609
4610 if (m_pCurrentStack)
4611 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4612 new_db_index); // highlite the correct bar entry
4613
4614 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4615
4616 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4617 proposed_scale_onscreen =
4618 wxMin(proposed_scale_onscreen,
4619 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4620 }
4621
4622 // set a minimum scale
4623 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4624 m_absolute_min_scale_ppm)
4625 proposed_scale_onscreen =
4626 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4627 }
4628
4629 double new_scale =
4630 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4631
4632 if (b_do_zoom) {
4633 // Disable ZTC if lookahead is ON, and currently b_follow is active
4634 bool b_allow_ztc = true;
4635 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4636 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4637 if (m_bLookAhead) {
4638 double brg, distance;
4639 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4640 &distance);
4641 dir_to_shift = brg;
4642 meters_to_shift = distance * 1852;
4643 }
4644 // Arrange to combine the zoom and pan into one operation for smoother
4645 // appearance
4646 SetVPScale(new_scale, false); // adjust, but deferred refresh
4647 wxPoint r;
4648 GetCanvasPointPix(zlat, zlon, &r);
4649 // this will emit the Refresh()
4650 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4651 } else {
4652 SetVPScale(new_scale);
4653 if (m_bFollow) DoCanvasUpdate();
4654 }
4655 }
4656
4657 m_bzooming = false;
4658}
4659
4660void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4661 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4662 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4663}
4664
4665int rot;
4666void ChartCanvas::RotateCanvas(double dir) {
4667 // SetUpMode(NORTH_UP_MODE);
4668
4669 if (g_bsmoothpanzoom) {
4670 if (StartTimedMovement()) {
4671 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4672 m_rotation_speed = dir * 60;
4673 }
4674 } else {
4675 double speed = dir * 10;
4676 if (m_modkeys == wxMOD_ALT) speed /= 20;
4677 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4678 }
4679}
4680
4681void ChartCanvas::DoRotateCanvas(double rotation) {
4682 while (rotation < 0) rotation += 2 * PI;
4683 while (rotation > 2 * PI) rotation -= 2 * PI;
4684
4685 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4686
4687 SetVPRotation(rotation);
4688 parent_frame->UpdateRotationState(VPoint.rotation);
4689}
4690
4691void ChartCanvas::DoTiltCanvas(double tilt) {
4692 while (tilt < 0) tilt = 0;
4693 while (tilt > .95) tilt = .95;
4694
4695 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4696
4697 VPoint.tilt = tilt;
4698 Refresh(false);
4699}
4700
4701void ChartCanvas::TogglebFollow(void) {
4702 if (!m_bFollow)
4703 SetbFollow();
4704 else
4705 ClearbFollow();
4706}
4707
4708void ChartCanvas::ClearbFollow(void) {
4709 m_bFollow = false; // update the follow flag
4710
4711 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4712
4713 UpdateFollowButtonState();
4714
4715 DoCanvasUpdate();
4716 ReloadVP();
4717 parent_frame->SetChartUpdatePeriod();
4718}
4719
4720void ChartCanvas::SetbFollow(void) {
4721 // Is the OWNSHIP on-screen?
4722 // If not, then reset the OWNSHIP offset to 0 (center screen)
4723 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4724 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4725 m_OSoffsetx = 0;
4726 m_OSoffsety = 0;
4727 }
4728
4729 // Apply the present b_follow offset values to ship position
4730 wxPoint2DDouble p;
4731 GetDoubleCanvasPointPix(gLat, gLon, &p);
4732 p.m_x += m_OSoffsetx;
4733 p.m_y -= m_OSoffsety;
4734
4735 // compute the target center screen lat/lon
4736 double dlat, dlon;
4737 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4738
4739 JumpToPosition(dlat, dlon, GetVPScale());
4740 m_bFollow = true;
4741
4742 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4743 UpdateFollowButtonState();
4744
4745 if (!g_bSmoothRecenter) {
4746 DoCanvasUpdate();
4747 ReloadVP();
4748 }
4749 parent_frame->SetChartUpdatePeriod();
4750}
4751
4752void ChartCanvas::UpdateFollowButtonState(void) {
4753 if (m_muiBar) {
4754 if (!m_bFollow)
4755 m_muiBar->SetFollowButtonState(0);
4756 else {
4757 if (m_bLookAhead)
4758 m_muiBar->SetFollowButtonState(2);
4759 else
4760 m_muiBar->SetFollowButtonState(1);
4761 }
4762 }
4763
4764#ifdef __ANDROID__
4765 if (!m_bFollow)
4766 androidSetFollowTool(0);
4767 else {
4768 if (m_bLookAhead)
4769 androidSetFollowTool(2);
4770 else
4771 androidSetFollowTool(1);
4772 }
4773#endif
4774
4775 // Look for plugin using API-121 or later
4776 // If found, make the follow state callback.
4777 if (g_pi_manager) {
4778 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4779 if (pic->m_enabled && pic->m_init_state) {
4780 switch (pic->m_api_version) {
4781 case 121: {
4782 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4783 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4784 break;
4785 }
4786 default:
4787 break;
4788 }
4789 }
4790 }
4791 }
4792}
4793
4794void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4795 if (g_bSmoothRecenter && !m_routeState) {
4796 if (StartSmoothJump(lat, lon, scale_ppm))
4797 return;
4798 else {
4799 // move closer to the target destination, and try again
4800 double gcDist, gcBearingEnd;
4801 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4802 &gcBearingEnd);
4803 gcBearingEnd += 180;
4804 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4805 GetCanvasWidth() / GetVPScale(); // meters
4806 double lon_offset =
4807 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4808 double new_lat = lat + (lat_offset / (1852 * 60));
4809 double new_lon = lon + (lon_offset / (1852 * 60));
4810 SetViewPoint(new_lat, new_lon);
4811 ReloadVP();
4812 StartSmoothJump(lat, lon, scale_ppm);
4813 return;
4814 }
4815 }
4816
4817 if (lon > 180.0) lon -= 360.0;
4818 m_vLat = lat;
4819 m_vLon = lon;
4820 StopMovement();
4821 m_bFollow = false;
4822
4823 if (!GetQuiltMode()) {
4824 double skew = 0;
4825 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4826 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4827 } else {
4828 if (scale_ppm != GetVPScale()) {
4829 // XXX should be done in SetViewPoint
4830 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4831 AdjustQuiltRefChart();
4832 }
4833 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4834 }
4835
4836 ReloadVP();
4837
4838 UpdateFollowButtonState();
4839
4840 // TODO
4841 // if( g_pi_manager ) {
4842 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4843 // }
4844}
4845
4846bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
4847 // Check distance to jump, in pixels at current chart scale
4848 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
4849 // width.
4850 double gcDist;
4851 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
4852 double distance_pixels = gcDist * GetVPScale();
4853 if (distance_pixels > 0.5 * GetCanvasWidth()) {
4854 // Jump is too far, try again
4855 return false;
4856 }
4857
4858 // Save where we're coming from
4859 m_startLat = m_vLat;
4860 m_startLon = m_vLon;
4861 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
4862
4863 // Save where we want to end up
4864 m_endLat = lat;
4865 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
4866 m_endScale = scale_ppm;
4867
4868 // Setup timing
4869 m_animationDuration = 600; // ms
4870 m_animationStart = wxGetLocalTimeMillis();
4871
4872 // Stop any previous movement, ensure no conflicts
4873 StopMovement();
4874 m_bFollow = false;
4875
4876 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
4877 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
4878 m_animationActive = true;
4879
4880 return true;
4881}
4882
4883void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
4884 // Calculate time fraction from 0..1
4885 wxLongLong now = wxGetLocalTimeMillis();
4886 double elapsed = (now - m_animationStart).ToDouble();
4887 double t = elapsed / m_animationDuration.ToDouble();
4888 if (t > 1.0) t = 1.0;
4889
4890 // Ease function for smoother movement
4891 double e = easeOutCubic(t);
4892
4893 // Interpolate lat/lon/scale
4894 double curLat = m_startLat + (m_endLat - m_startLat) * e;
4895 double curLon = m_startLon + (m_endLon - m_startLon) * e;
4896 double curScale = m_startScale + (m_endScale - m_startScale) * e;
4897
4898 // Update viewpoint
4899 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
4900 // portion)
4901 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
4902 ReloadVP();
4903
4904 // If we reached the end, stop the timer and finalize
4905 if (t >= 1.0) {
4906 m_easeTimer.Stop();
4907 m_animationActive = false;
4908 UpdateFollowButtonState();
4909 ZoomCanvasSimple(1.0001);
4910 DoCanvasUpdate();
4911 ReloadVP();
4912 }
4913}
4914
4915bool ChartCanvas::PanCanvas(double dx, double dy) {
4916 if (!ChartData) return false;
4917
4918 extendedSectorLegs.clear();
4919
4920 double dlat, dlon;
4921 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
4922
4923 int iters = 0;
4924 for (;;) {
4925 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
4926
4927 if (iters++ > 5) return false;
4928 if (!std::isnan(dlat)) break;
4929
4930 dx *= .5, dy *= .5;
4931 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
4932 }
4933
4934 // avoid overshooting the poles
4935 if (dlat > 90)
4936 dlat = 90;
4937 else if (dlat < -90)
4938 dlat = -90;
4939
4940 if (dlon > 360.) dlon -= 360.;
4941 if (dlon < -360.) dlon += 360.;
4942
4943 // This should not really be necessary, but round-trip georef on some
4944 // charts is not perfect, So we can get creep on repeated unidimensional
4945 // pans, and corrupt chart cacheing.......
4946
4947 // But this only works on north-up projections
4948 // TODO: can we remove this now?
4949 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
4950 // .001 ) ) {
4951 //
4952 // if( dx == 0 ) dlon = clon;
4953 // if( dy == 0 ) dlat = clat;
4954 // }
4955
4956 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
4957
4958 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
4959
4960 if (VPoint.b_quilt) {
4961 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
4962 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
4963 // Tweak the scale slightly for a new ref chart
4964 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
4965 if (pc) {
4966 double tweak_scale_ppm =
4967 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
4968 SetVPScale(tweak_scale_ppm);
4969 }
4970 }
4971
4972 if (new_ref_dbIndex == -1) {
4973#pragma GCC diagnostic push
4974#pragma GCC diagnostic ignored "-Warray-bounds"
4975 // The compiler sees a -1 index being used. Does not happen, though.
4976
4977 // for whatever reason, no reference chart is known
4978 // Probably panned out of the coverage region
4979 // If any charts are anywhere on-screen, choose the smallest
4980 // scale chart on the screen to be a new reference chart.
4981 int trial_index = -1;
4982 if (m_pCurrentStack->nEntry) {
4983 int trial_index =
4984 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4985 }
4986
4987 if (trial_index < 0) {
4988 auto full_screen_array = GetQuiltFullScreendbIndexArray();
4989 if (full_screen_array.size())
4990 trial_index = full_screen_array[full_screen_array.size() - 1];
4991 }
4992
4993 if (trial_index >= 0) {
4994 m_pQuilt->SetReferenceChart(trial_index);
4995 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
4996 VPoint.rotation);
4997 ReloadVP();
4998 }
4999#pragma GCC diagnostic pop
5000 }
5001 }
5002
5003 // Turn off bFollow only if the ownship has left the screen
5004 if (m_bFollow) {
5005 double offx, offy;
5006 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5007
5008 double offset_angle = atan2(offy, offx);
5009 double offset_distance = sqrt((offy * offy) + (offx * offx));
5010 double chart_angle = GetVPRotation();
5011 double target_angle = chart_angle - offset_angle;
5012 double d_east_mod = offset_distance * cos(target_angle);
5013 double d_north_mod = offset_distance * sin(target_angle);
5014
5015 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5016 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5017
5018 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5019 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5020 m_bFollow = false; // update the follow flag
5021 UpdateFollowButtonState();
5022 }
5023 }
5024
5025 Refresh(false);
5026
5027 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5028
5029 return true;
5030}
5031
5032bool ChartCanvas::IsOwnshipOnScreen() {
5033 wxPoint r;
5034 GetCanvasPointPix(gLat, gLon, &r);
5035 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5036 ((r.y > 0) && r.y < GetCanvasHeight()))
5037 return true;
5038 else
5039 return false;
5040}
5041
5042void ChartCanvas::ReloadVP(bool b_adjust) {
5043 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5044
5045 LoadVP(VPoint, b_adjust);
5046}
5047
5048void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5049#ifdef ocpnUSE_GL
5050 if (g_bopengl && m_glcc) {
5051 m_glcc->Invalidate();
5052 if (m_glcc->GetSize() != GetSize()) {
5053 m_glcc->SetSize(GetSize());
5054 }
5055 } else
5056#endif
5057 {
5058 m_cache_vp.Invalidate();
5059 m_bm_cache_vp.Invalidate();
5060 }
5061
5062 VPoint.Invalidate();
5063
5064 if (m_pQuilt) m_pQuilt->Invalidate();
5065
5066 // Make sure that the Selected Group is sensible...
5067 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5068 // m_groupIndex = 0;
5069 // if( !CheckGroup( m_groupIndex ) )
5070 // m_groupIndex = 0;
5071
5072 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5073 vp.m_projection_type, b_adjust);
5074}
5075
5076void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5077 m_pQuilt->SetReferenceChart(dbIndex);
5078 VPoint.Invalidate();
5079 m_pQuilt->Invalidate();
5080}
5081
5082double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5083 if (m_pQuilt)
5084 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5085 else
5086 return vp.view_scale_ppm;
5087}
5088
5089// Verify and adjust the current reference chart,
5090// so that it will not lead to excessive overzoom or underzoom onscreen
5091int ChartCanvas::AdjustQuiltRefChart() {
5092 int ret = -1;
5093 if (m_pQuilt) {
5094 wxASSERT(ChartData);
5095 ChartBase *pc =
5096 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5097 if (pc) {
5098 double min_ref_scale =
5099 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5100 double max_ref_scale =
5101 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5102
5103 if (VPoint.chart_scale < min_ref_scale) {
5104 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5105 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5106 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5107 } else {
5108 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5109
5110 if (!brender_ok) {
5111 int target_stack_index = wxNOT_FOUND;
5112 int il = 0;
5113 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5114 if (index == m_pQuilt->GetRefChartdbIndex()) {
5115 target_stack_index = il;
5116 break;
5117 }
5118 il++;
5119 }
5120 if (wxNOT_FOUND == target_stack_index) // should never happen...
5121 target_stack_index = 0;
5122
5123 int ref_family = pc->GetChartFamily();
5124 int extended_array_count =
5125 m_pQuilt->GetExtendedStackIndexArray().size();
5126 while ((!brender_ok) &&
5127 ((int)target_stack_index < (extended_array_count - 1))) {
5128 target_stack_index++;
5129 int test_db_index =
5130 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5131
5132 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5133 IsChartQuiltableRef(test_db_index)) {
5134 // open the target, and check the min_scale
5135 ChartBase *ptest_chart =
5136 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5137 if (ptest_chart) {
5138 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5139 }
5140 }
5141 }
5142
5143 if (brender_ok) { // found a better reference chart
5144 int new_db_index =
5145 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5146 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5147 IsChartQuiltableRef(new_db_index)) {
5148 m_pQuilt->SetReferenceChart(new_db_index);
5149 ret = new_db_index;
5150 } else
5151 ret = m_pQuilt->GetRefChartdbIndex();
5152 } else
5153 ret = m_pQuilt->GetRefChartdbIndex();
5154
5155 } else
5156 ret = m_pQuilt->GetRefChartdbIndex();
5157 }
5158 } else
5159 ret = -1;
5160 }
5161
5162 return ret;
5163}
5164
5165void ChartCanvas::UpdateCanvasOnGroupChange(void) {
5166 delete m_pCurrentStack;
5167 m_pCurrentStack = new ChartStack;
5168 wxASSERT(ChartData);
5169 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5170 m_groupIndex);
5171
5172 if (m_pQuilt) {
5173 m_pQuilt->Compose(VPoint);
5174 SetFocus();
5175 }
5176}
5177
5178bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5179 double latNE, double lonNE) {
5180 // Center Point
5181 double latc = (latSW + latNE) / 2.0;
5182 double lonc = (lonSW + lonNE) / 2.0;
5183
5184 // Get scale in ppm (latitude)
5185 double ne_easting, ne_northing;
5186 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5187
5188 double sw_easting, sw_northing;
5189 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5190
5191 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5192
5193 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5194}
5195
5196bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5197 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5198 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5199}
5200
5201bool ChartCanvas::SetVPProjection(int projection) {
5202 if (!g_bopengl) // alternative projections require opengl
5203 return false;
5204
5205 // the view scale varies depending on geographic location and projection
5206 // rescale to keep the relative scale on the screen the same
5207 double prev_true_scale_ppm = m_true_scale_ppm;
5208 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5209 VPoint.skew, VPoint.rotation, projection) &&
5210 SetVPScale(wxMax(
5211 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5212 m_absolute_min_scale_ppm));
5213}
5214
5215bool ChartCanvas::SetViewPoint(double lat, double lon) {
5216 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5217 VPoint.rotation);
5218}
5219
5220bool ChartCanvas::SetVPRotation(double angle) {
5221 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5222 VPoint.skew, angle);
5223}
5224bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5225 double skew, double rotation, int projection,
5226 bool b_adjust, bool b_refresh) {
5227 bool b_ret = false;
5228 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5229 skew -= 2 * PI;
5230 // Any sensible change?
5231 if (VPoint.IsValid()) {
5232 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5233 (fabs(VPoint.skew - skew) < 1e-9) &&
5234 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5235 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5236 (VPoint.m_projection_type == projection ||
5237 projection == PROJECTION_UNKNOWN))
5238 return false;
5239 }
5240 if (VPoint.m_projection_type != projection)
5241 VPoint.InvalidateTransformCache(); // invalidate
5242
5243 // Take a local copy of the last viewport
5244 ViewPort last_vp = VPoint;
5245
5246 VPoint.skew = skew;
5247 VPoint.clat = lat;
5248 VPoint.clon = lon;
5249 VPoint.rotation = rotation;
5250 VPoint.view_scale_ppm = scale_ppm;
5251 if (projection != PROJECTION_UNKNOWN)
5252 VPoint.SetProjectionType(projection);
5253 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5254 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5255
5256 // don't allow latitude above 88 for mercator (90 is infinity)
5257 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5258 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5259 if (VPoint.clat > 89.5)
5260 VPoint.clat = 89.5;
5261 else if (VPoint.clat < -89.5)
5262 VPoint.clat = -89.5;
5263 }
5264
5265 // don't zoom out too far for transverse mercator polyconic until we resolve
5266 // issues
5267 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5268 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5269 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5270
5271 // SetVPRotation(rotation);
5272
5273 if (!g_bopengl) // tilt is not possible without opengl
5274 VPoint.tilt = 0;
5275
5276 if ((VPoint.pix_width <= 0) ||
5277 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5278 return false;
5279
5280 bool bwasValid = VPoint.IsValid();
5281 VPoint.Validate(); // Mark this ViewPoint as OK
5282
5283 // Has the Viewport scale changed? If so, invalidate the vp
5284 if (last_vp.view_scale_ppm != scale_ppm) {
5285 m_cache_vp.Invalidate();
5286 InvalidateGL();
5287 }
5288
5289 // A preliminary value, may be tweaked below
5290 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5291
5292 // recompute cursor position
5293 // and send to interested plugins if the mouse is actually in this window
5294 int mouseX = mouse_x;
5295 int mouseY = mouse_y;
5296 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5297 (mouseY < VPoint.pix_height)) {
5298 double lat, lon;
5299 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5300 m_cursor_lat = lat;
5301 m_cursor_lon = lon;
5302 SendCursorLatLonToAllPlugIns(lat, lon);
5303 }
5304
5305 if (!VPoint.b_quilt && m_singleChart) {
5306 VPoint.SetBoxes();
5307
5308 // Allow the chart to adjust the new ViewPort for performance optimization
5309 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5310 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5311
5312 // If there is a sensible change in the chart render, refresh the whole
5313 // screen
5314 if ((!m_cache_vp.IsValid()) ||
5315 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5316 Refresh(false);
5317 b_ret = true;
5318 } else {
5319 wxPoint cp_last, cp_this;
5320 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5321 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5322
5323 if (cp_last != cp_this) {
5324 Refresh(false);
5325 b_ret = true;
5326 }
5327 }
5328 // Create the stack
5329 if (m_pCurrentStack) {
5330 assert(ChartData != 0);
5331 int current_db_index;
5332 current_db_index =
5333 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5334
5335 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5336 m_groupIndex);
5337 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5338 }
5339
5340 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5341 }
5342
5343 // Handle the quilted case
5344 if (VPoint.b_quilt) {
5345 if (last_vp.view_scale_ppm != scale_ppm)
5346 m_pQuilt->InvalidateAllQuiltPatchs();
5347
5348 // Create the quilt
5349 if (ChartData /*&& ChartData->IsValid()*/) {
5350 if (!m_pCurrentStack) return false;
5351
5352 int current_db_index;
5353 current_db_index =
5354 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5355
5356 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5357 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5358
5359 // Check to see if the current quilt reference chart is in the new stack
5360 int current_ref_stack_index = -1;
5361 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5362 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5363 current_ref_stack_index = i;
5364 }
5365
5366 if (g_bFullScreenQuilt) {
5367 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5368 }
5369
5370 // We might need a new Reference Chart
5371 bool b_needNewRef = false;
5372
5373 // If the new stack does not contain the current ref chart....
5374 if ((-1 == current_ref_stack_index) &&
5375 (m_pQuilt->GetRefChartdbIndex() >= 0))
5376 b_needNewRef = true;
5377
5378 // Would the current Ref Chart be excessively underzoomed?
5379 // We need to check this here to be sure, since we cannot know where the
5380 // reference chart was assigned. For instance, the reference chart may
5381 // have been selected from the config file, or from a long jump with a
5382 // chart family switch implicit. Anyway, we check to be sure....
5383 bool renderable = true;
5384 ChartBase *referenceChart =
5385 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5386 if (referenceChart) {
5387 double chartMaxScale = referenceChart->GetNormalScaleMax(
5388 GetCanvasScaleFactor(), GetCanvasWidth());
5389 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5390 }
5391 if (!renderable) b_needNewRef = true;
5392
5393 // Need new refchart?
5394 if (b_needNewRef) {
5395 const ChartTableEntry &cte_ref =
5396 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5397 int target_scale = cte_ref.GetScale();
5398 int target_type = cte_ref.GetChartType();
5399 int candidate_stack_index;
5400
5401 // reset the ref chart in a way that does not lead to excessive
5402 // underzoom, for performance reasons Try to find a chart that is the
5403 // same type, and has a scale of just smaller than the current ref
5404 // chart
5405
5406 candidate_stack_index = 0;
5407 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5408 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5409 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5410 int candidate_scale = cte_candidate.GetScale();
5411 int candidate_type = cte_candidate.GetChartType();
5412
5413 if ((candidate_scale >= target_scale) &&
5414 (candidate_type == target_type)) {
5415 bool renderable = true;
5416 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5417 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5418 if (tentative_referenceChart) {
5419 double chartMaxScale =
5420 tentative_referenceChart->GetNormalScaleMax(
5421 GetCanvasScaleFactor(), GetCanvasWidth());
5422 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5423 }
5424
5425 if (renderable) break;
5426 }
5427
5428 candidate_stack_index++;
5429 }
5430
5431 // If that did not work, look for a chart of just larger scale and
5432 // same type
5433 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5434 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5435 while (candidate_stack_index >= 0) {
5436 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5437 if (idx >= 0) {
5438 const ChartTableEntry &cte_candidate =
5439 ChartData->GetChartTableEntry(idx);
5440 int candidate_scale = cte_candidate.GetScale();
5441 int candidate_type = cte_candidate.GetChartType();
5442
5443 if ((candidate_scale <= target_scale) &&
5444 (candidate_type == target_type))
5445 break;
5446 }
5447 candidate_stack_index--;
5448 }
5449 }
5450
5451 // and if that did not work, chose stack entry 0
5452 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5453 (candidate_stack_index < 0))
5454 candidate_stack_index = 0;
5455
5456 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5457
5458 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5459 }
5460
5461 if (!g_bopengl) {
5462 // Preset the VPoint projection type to match what the quilt projection
5463 // type will be
5464 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5465
5466 // Always keep the default Mercator projection if the reference chart is
5467 // not in the PatchList or the scale is too small for it to render.
5468
5469 bool renderable = true;
5470 ChartBase *referenceChart =
5471 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5472 if (referenceChart) {
5473 double chartMaxScale = referenceChart->GetNormalScaleMax(
5474 GetCanvasScaleFactor(), GetCanvasWidth());
5475 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5476 proj = ChartData->GetDBChartProj(ref_db_index);
5477 } else
5478 proj = PROJECTION_MERCATOR;
5479
5480 VPoint.b_MercatorProjectionOverride =
5481 (m_pQuilt->GetnCharts() == 0 || !renderable);
5482
5483 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5484
5485 VPoint.SetProjectionType(proj);
5486 }
5487
5488 VPoint.SetBoxes();
5489
5490 // If this quilt will be a perceptible delta from the existing quilt,
5491 // then refresh the entire screen
5492 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5493 // Allow the quilt to adjust the new ViewPort for performance
5494 // optimization This will normally be only a fractional (i.e.
5495 // sub-pixel) adjustment...
5496 if (b_adjust) {
5497 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5498 }
5499
5500 // ChartData->ClearCacheInUseFlags();
5501 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5502
5503 // wxStopWatch sw;
5504
5505#ifdef __ANDROID__
5506 // This is an optimization for panning on touch screen systems.
5507 // The quilt composition is deferred until the OnPaint() message gets
5508 // finally removed and processed from the message queue.
5509 // Takes advantage of the fact that touch-screen pan gestures are
5510 // usually short in distance,
5511 // so not requiring a full quilt rebuild until the pan gesture is
5512 // complete.
5513 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5514 // qDebug() << "Force compose";
5515 m_pQuilt->Compose(VPoint);
5516 } else {
5517 m_pQuilt->Invalidate();
5518 }
5519#else
5520 m_pQuilt->Compose(VPoint);
5521#endif
5522
5523 // printf("comp time %ld\n", sw.Time());
5524
5525 // If the extended chart stack has changed, invalidate any cached
5526 // render bitmap
5527 // if(m_pQuilt->GetXStackHash() != hash1) {
5528 // m_bm_cache_vp.Invalidate();
5529 // InvalidateGL();
5530 // }
5531
5532 ChartData->PurgeCacheUnusedCharts(0.7);
5533
5534 if (b_refresh) Refresh(false);
5535
5536 b_ret = true;
5537 }
5538 }
5539
5540 VPoint.skew = 0.; // Quilting supports 0 Skew
5541 } else if (!g_bopengl) {
5542 OcpnProjType projection = PROJECTION_UNKNOWN;
5543 if (m_singleChart) // viewport projection must match chart projection
5544 // without opengl
5545 projection = m_singleChart->GetChartProjectionType();
5546 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5547 VPoint.SetProjectionType(projection);
5548 }
5549
5550 // Has the Viewport projection changed? If so, invalidate the vp
5551 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5552 m_cache_vp.Invalidate();
5553 InvalidateGL();
5554 }
5555
5556 UpdateCanvasControlBar(); // Refresh the Piano
5557
5558 VPoint.chart_scale = 1.0; // fallback default value
5559
5560 /*if (!VPoint.GetBBox().GetValid())*/ VPoint.SetBoxes();
5561
5562 if (VPoint.GetBBox().GetValid()) {
5563 // Update the viewpoint reference scale
5564 if (m_singleChart)
5565 VPoint.ref_scale = m_singleChart->GetNativeScale();
5566 else {
5567#ifdef __ANDROID__
5568 // This is an optimization for panning on touch screen systems.
5569 // See above.
5570 // Quilt might not be fully composed at this point, so for cm93
5571 // the reference scale may not be known.
5572 // In this case, do not update the VP ref_scale.
5573 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5574 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5575 }
5576#else
5577 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5578#endif
5579 }
5580
5581 // Calculate the on-screen displayed actual scale
5582 // by a simple traverse northward from the center point
5583 // of roughly one eighth of the canvas height
5584 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5585
5586 double delta_check =
5587 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5588 delta_check /= 8.;
5589
5590 double check_point = wxMin(89., VPoint.clat);
5591
5592 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5593
5594 double rhumbDist;
5595 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5596 VPoint.clon, 0, &rhumbDist);
5597
5598 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5599 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5600 // Calculate the distance between r1 and r in physical pixels.
5601 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5602 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5603
5604 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5605
5606 // A fall back in case of very high zoom-out, giving delta_y == 0
5607 // which can probably only happen with vector charts
5608 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5609
5610 // Another fallback, for highly zoomed out charts
5611 // This adjustment makes the displayed TrueScale correspond to the
5612 // same algorithm used to calculate the chart zoom-out limit for
5613 // ChartDummy.
5614 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5615
5616 if (m_true_scale_ppm)
5617 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5618 else
5619 VPoint.chart_scale = 1.0;
5620
5621 // Create a nice renderable string
5622 double round_factor = 1000.;
5623 if (VPoint.chart_scale <= 1000.)
5624 round_factor = 10.;
5625 else if (VPoint.chart_scale <= 10000.)
5626 round_factor = 100.;
5627 else if (VPoint.chart_scale <= 100000.)
5628 round_factor = 1000.;
5629
5630 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5631 double retina_coef = 1;
5632#ifdef ocpnUSE_GL
5633#ifdef __WXOSX__
5634 if (g_bopengl) {
5635 retina_coef = GetContentScaleFactor();
5636 }
5637#endif
5638#endif
5639
5640 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5641 // rounded to the nearest 10, 100 or 1000.
5642 //
5643 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5644 // true_scale_display. That does not make sense. The chart scale should be
5645 // the same as the true scale within the limits of the rounding factor.
5646 double true_scale_display =
5647 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5648 wxString text;
5649
5650 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5651
5652 if (m_displayed_scale_factor > 10.0)
5653 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5654 m_displayed_scale_factor);
5655 else if (m_displayed_scale_factor > 1.0)
5656 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5657 m_displayed_scale_factor);
5658 else if (m_displayed_scale_factor > 0.1) {
5659 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5660 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5661 } else if (m_displayed_scale_factor > 0.01) {
5662 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5663 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5664 } else {
5665 text.Printf(
5666 "%s %4.0f (---)", _("Scale"),
5667 true_scale_display); // Generally, no chart, so no chart scale factor
5668 }
5669
5670 m_scaleValue = true_scale_display;
5671 m_scaleText = text;
5672 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5673
5674 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5675 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5676 // Check to see if the text will fit in the StatusBar field...
5677 bool b_noshow = false;
5678 {
5679 int w = 0;
5680 int h;
5681 wxClientDC dc(parent_frame->GetStatusBar());
5682 if (dc.IsOk()) {
5683 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5684 dc.SetFont(*templateFont);
5685 dc.GetTextExtent(text, &w, &h);
5686
5687 // If text is too long for the allocated field, try to reduce the text
5688 // string a bit.
5689 wxRect rect;
5690 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5691 if (w && w > rect.width) {
5692 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5693 }
5694
5695 // Test again...if too big still, then give it up.
5696 dc.GetTextExtent(text, &w, &h);
5697
5698 if (w && w > rect.width) {
5699 b_noshow = true;
5700 }
5701 }
5702 }
5703
5704 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5705 }
5706 }
5707
5708 // Maintain member vLat/vLon
5709 m_vLat = VPoint.clat;
5710 m_vLon = VPoint.clon;
5711
5712 return b_ret;
5713}
5714
5715// Static Icon definitions for some symbols requiring
5716// scaling/rotation/translation Very specific wxDC draw commands are
5717// necessary to properly render these icons...See the code in
5718// ShipDraw()
5719
5720// This icon was adapted and scaled from the S52 Presentation Library
5721// version 3_03.
5722// Symbol VECGND02
5723
5724static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5725
5726// This ownship icon was adapted and scaled from the S52 Presentation
5727// Library version 3_03 Symbol OWNSHP05
5728static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5729 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5730
5731wxColour ChartCanvas::PredColor() {
5732 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5733 // visibility.
5734 if (SHIP_NORMAL == m_ownship_state)
5735 return GetGlobalColor(_T ( "URED" ));
5736
5737 else if (SHIP_LOWACCURACY == m_ownship_state)
5738 return GetGlobalColor(_T ( "YELO1" ));
5739
5740 return GetGlobalColor(_T ( "NODTA" ));
5741}
5742
5743wxColour ChartCanvas::ShipColor() {
5744 // Establish ship color
5745 // It changes color based on GPS and Chart accuracy/availability
5746
5747 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor(_T ( "GREY1" ));
5748
5749 if (SHIP_LOWACCURACY == m_ownship_state)
5750 return GetGlobalColor(_T ( "YELO1" ));
5751
5752 return GetGlobalColor(_T ( "URED" )); // default is OK
5753}
5754
5755void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5756 wxPoint2DDouble lShipMidPoint) {
5757 dc.SetPen(wxPen(PredColor(), 2));
5758
5759 if (SHIP_NORMAL == m_ownship_state)
5760 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5761 else
5762 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "YELO1" ))));
5763
5764 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5765 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5766
5767 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5768 lShipMidPoint.m_y);
5769 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5770 lShipMidPoint.m_y + 12);
5771}
5772
5773void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5774 wxPoint GPSOffsetPixels,
5775 wxPoint2DDouble lGPSPoint) {
5776 // if (m_animationActive) return;
5777 // Develop a uniform length for course predictor line dash length, based on
5778 // physical display size Use this reference length to size all other graphics
5779 // elements
5780 float ref_dim = m_display_size_mm / 24;
5781 ref_dim = wxMin(ref_dim, 12);
5782 ref_dim = wxMax(ref_dim, 6);
5783
5784 wxColour cPred;
5785 cPred.Set(g_cog_predictor_color);
5786 if (cPred == wxNullColour) cPred = PredColor();
5787
5788 // Establish some graphic element line widths dependent on the platform
5789 // display resolution
5790 // double nominal_line_width_pix = wxMax(1.0,
5791 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5792 // not less than 1 pixel
5793 double nominal_line_width_pix = wxMax(
5794 1.0,
5795 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5796
5797 // If the calculated value is greater than the config file spec value, then
5798 // use it.
5799 if (nominal_line_width_pix > g_cog_predictor_width)
5800 g_cog_predictor_width = nominal_line_width_pix;
5801
5802 // Calculate ownship Position Predictor
5803 wxPoint lPredPoint, lHeadPoint;
5804
5805 float pCog = std::isnan(gCog) ? 0 : gCog;
5806 float pSog = std::isnan(gSog) ? 0 : gSog;
5807
5808 double pred_lat, pred_lon;
5809 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5810 &pred_lat, &pred_lon);
5811 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5812
5813 // test to catch the case where COG/HDG line crosses the screen
5814 LLBBox box;
5815
5816 // Should we draw the Head vector?
5817 // Compare the points lHeadPoint and lPredPoint
5818 // If they differ by more than n pixels, and the head vector is valid, then
5819 // render the head vector
5820
5821 float ndelta_pix = 10.;
5822 double hdg_pred_lat, hdg_pred_lon;
5823 bool b_render_hdt = false;
5824 if (!std::isnan(gHdt)) {
5825 // Calculate ownship Heading pointer as a predictor
5826 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5827 &hdg_pred_lon);
5828 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5829 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5830 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5831 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5832 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5833 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5834 }
5835 }
5836
5837 // draw course over ground if they are longer than the ship
5838 wxPoint lShipMidPoint;
5839 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5840 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5841 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5842 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5843
5844 if (lpp >= img_height / 2) {
5845 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5846 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5847 !std::isnan(gSog)) {
5848 // COG Predictor
5849 float dash_length = ref_dim;
5850 wxDash dash_long[2];
5851 dash_long[0] =
5852 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
5853 g_cog_predictor_width); // Long dash , in mm <---------+
5854 dash_long[1] = dash_long[0] / 2.0; // Short gap
5855
5856 // On ultra-hi-res displays, do not allow the dashes to be greater than
5857 // 250, since it is defined as (char)
5858 if (dash_length > 250.) {
5859 dash_long[0] = 250. / g_cog_predictor_width;
5860 dash_long[1] = dash_long[0] / 2;
5861 }
5862
5863 wxPen ppPen2(cPred, g_cog_predictor_width,
5864 (wxPenStyle)g_cog_predictor_style);
5865 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
5866 ppPen2.SetDashes(2, dash_long);
5867 dc.SetPen(ppPen2);
5868 dc.StrokeLine(
5869 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
5870 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
5871
5872 if (g_cog_predictor_width > 1) {
5873 float line_width = g_cog_predictor_width / 3.;
5874
5875 wxDash dash_long3[2];
5876 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
5877 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
5878
5879 wxPen ppPen3(GetGlobalColor(_T ( "UBLCK" )), wxMax(1, line_width),
5880 (wxPenStyle)g_cog_predictor_style);
5881 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
5882 ppPen3.SetDashes(2, dash_long3);
5883 dc.SetPen(ppPen3);
5884 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
5885 lGPSPoint.m_y + GPSOffsetPixels.y,
5886 lPredPoint.x + GPSOffsetPixels.x,
5887 lPredPoint.y + GPSOffsetPixels.y);
5888 }
5889
5890 if (g_cog_predictor_endmarker) {
5891 // Prepare COG predictor endpoint icon
5892 double png_pred_icon_scale_factor = .4;
5893 if (g_ShipScaleFactorExp > 1.0)
5894 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
5895 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
5896
5897 wxPoint icon[4];
5898
5899 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
5900 (float)(lPredPoint.x - lShipMidPoint.x));
5901 cog_rad += (float)PI;
5902
5903 for (int i = 0; i < 4; i++) {
5904 int j = i * 2;
5905 double pxa = (double)(s_png_pred_icon[j]);
5906 double pya = (double)(s_png_pred_icon[j + 1]);
5907
5908 pya *= png_pred_icon_scale_factor;
5909 pxa *= png_pred_icon_scale_factor;
5910
5911 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
5912 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
5913
5914 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
5915 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
5916 }
5917
5918 // Render COG endpoint icon
5919 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), g_cog_predictor_width / 2,
5920 wxPENSTYLE_SOLID);
5921 dc.SetPen(ppPen1);
5922 dc.SetBrush(wxBrush(cPred));
5923
5924 dc.StrokePolygon(4, icon);
5925 }
5926 }
5927 }
5928
5929 // HDT Predictor
5930 if (b_render_hdt) {
5931 float hdt_dash_length = ref_dim * 0.4;
5932
5933 cPred.Set(g_ownship_HDTpredictor_color);
5934 if (cPred == wxNullColour) cPred = PredColor();
5935 float hdt_width =
5936 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
5937 : g_cog_predictor_width * 0.8);
5938 wxDash dash_short[2];
5939 dash_short[0] =
5940 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
5941 hdt_width); // Short dash , in mm <---------+
5942 dash_short[1] =
5943 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
5944 hdt_width); // Short gap |
5945
5946 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
5947 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
5948 ppPen2.SetDashes(2, dash_short);
5949
5950 dc.SetPen(ppPen2);
5951 dc.StrokeLine(
5952 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
5953 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
5954
5955 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
5956 dc.SetPen(ppPen1);
5957 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "GREY2" ))));
5958
5959 if (g_ownship_HDTpredictor_endmarker) {
5960 double nominal_circle_size_pixels = wxMax(
5961 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
5962
5963 // Scale the circle to ChartScaleFactor, slightly softened....
5964 if (g_ShipScaleFactorExp > 1.0)
5965 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
5966
5967 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
5968 lHeadPoint.y + GPSOffsetPixels.y,
5969 nominal_circle_size_pixels / 2);
5970 }
5971 }
5972
5973 // Draw radar rings if activated
5974 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
5975 double factor = 1.00;
5976 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
5977 factor = 1 / 1.852;
5978 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
5979 if (std::isnan(gSog))
5980 factor = 0.0;
5981 else
5982 factor = gSog / 60;
5983 }
5984 factor *= g_fNavAidRadarRingsStep;
5985
5986 double tlat, tlon;
5987 wxPoint r;
5988 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
5989 GetCanvasPointPix(tlat, tlon, &r);
5990
5991 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
5992 pow((double)(lGPSPoint.m_y - r.y), 2));
5993 int pix_radius = (int)lpp;
5994
5995 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
5996
5997 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
5998
5999 dc.SetPen(ppPen1);
6000 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6001
6002 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6003 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6004 }
6005}
6006
6007void ChartCanvas::ComputeShipScaleFactor(
6008 float icon_hdt, int ownShipWidth, int ownShipLength,
6009 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6010 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6011 float screenResolution = m_pix_per_mm;
6012
6013 // Calculate the true ship length in exact pixels
6014 double ship_bow_lat, ship_bow_lon;
6015 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6016 &ship_bow_lat, &ship_bow_lon);
6017 wxPoint lShipBowPoint;
6018 wxPoint2DDouble b_point =
6019 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6020 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6021
6022 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6023 powf((float)(b_point.m_y - a_point.m_y), 2));
6024
6025 // And in mm
6026 float shipLength_mm = shipLength_px / screenResolution;
6027
6028 // Set minimum ownship drawing size
6029 float ownship_min_mm = g_n_ownship_min_mm;
6030 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6031
6032 // Calculate Nautical Miles distance from midships to gps antenna
6033 float hdt_ant = icon_hdt + 180.;
6034 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6035 float dx = g_n_gps_antenna_offset_x / 1852.;
6036 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6037 {
6038 hdt_ant = icon_hdt;
6039 dy = -dy;
6040 }
6041
6042 // If the drawn ship size is going to be clamped, adjust the gps antenna
6043 // offsets
6044 if (shipLength_mm < ownship_min_mm) {
6045 dy /= shipLength_mm / ownship_min_mm;
6046 dx /= shipLength_mm / ownship_min_mm;
6047 }
6048
6049 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6050
6051 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6052 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6053 &ship_mid_lon1);
6054
6055 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6056 &lShipMidPoint);
6057
6058 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6059 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6060
6061 float scale_factor = shipLength_px / ownShipLength;
6062
6063 // Calculate a scale factor that would produce a reasonably sized icon
6064 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6065
6066 // And choose the correct one
6067 scale_factor = wxMax(scale_factor, scale_factor_min);
6068
6069 scale_factor_y = scale_factor;
6070 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6071 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6072}
6073
6074void ChartCanvas::ShipDraw(ocpnDC &dc) {
6075 if (!GetVP().IsValid()) return;
6076
6077 wxPoint GPSOffsetPixels(0, 0);
6078 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6079
6080 // COG/SOG may be undefined in NMEA data stream
6081 float pCog = std::isnan(gCog) ? 0 : gCog;
6082 float pSog = std::isnan(gSog) ? 0 : gSog;
6083
6084 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6085
6086 lShipMidPoint = lGPSPoint;
6087
6088 // Draw the icon rotated to the COG
6089 // or to the Hdt if available
6090 float icon_hdt = pCog;
6091 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6092
6093 // COG may be undefined in NMEA data stream
6094 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6095
6096 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6097 // predictor
6098 double osd_head_lat, osd_head_lon;
6099 wxPoint osd_head_point;
6100
6101 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6102 &osd_head_lon);
6103
6104 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6105
6106 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6107 (float)(osd_head_point.x - lShipMidPoint.m_x));
6108 icon_rad += (float)PI;
6109
6110 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6111
6112 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6113 // nominal size and is just barely outside the viewport ....
6114 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6115
6116 // TODO: fix to include actual size of boat that will be rendered
6117 int img_height = 0;
6118 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6119 if (GetVP().chart_scale >
6120 300000) // According to S52, this should be 50,000
6121 {
6122 ShipDrawLargeScale(dc, lShipMidPoint);
6123 img_height = 20;
6124 } else {
6125 wxImage pos_image;
6126
6127 // Substitute user ownship image if found
6128 if (m_pos_image_user)
6129 pos_image = m_pos_image_user->Copy();
6130 else if (SHIP_NORMAL == m_ownship_state)
6131 pos_image = m_pos_image_red->Copy();
6132 if (SHIP_LOWACCURACY == m_ownship_state)
6133 pos_image = m_pos_image_yellow->Copy();
6134 else if (SHIP_NORMAL != m_ownship_state)
6135 pos_image = m_pos_image_grey->Copy();
6136
6137 // Substitute user ownship image if found
6138 if (m_pos_image_user) {
6139 pos_image = m_pos_image_user->Copy();
6140
6141 if (SHIP_LOWACCURACY == m_ownship_state)
6142 pos_image = m_pos_image_user_yellow->Copy();
6143 else if (SHIP_NORMAL != m_ownship_state)
6144 pos_image = m_pos_image_user_grey->Copy();
6145 }
6146
6147 img_height = pos_image.GetHeight();
6148
6149 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6150 g_OwnShipIconType > 0) // use large ship
6151 {
6152 int ownShipWidth = 22; // Default values from s_ownship_icon
6153 int ownShipLength = 84;
6154 if (g_OwnShipIconType == 1) {
6155 ownShipWidth = pos_image.GetWidth();
6156 ownShipLength = pos_image.GetHeight();
6157 }
6158
6159 float scale_factor_x, scale_factor_y;
6160 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6161 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6162 scale_factor_x, scale_factor_y);
6163
6164 if (g_OwnShipIconType == 1) { // Scaled bitmap
6165 pos_image.Rescale(ownShipWidth * scale_factor_x,
6166 ownShipLength * scale_factor_y,
6167 wxIMAGE_QUALITY_HIGH);
6168 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6169 wxImage rot_image =
6170 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6171
6172 // Simple sharpening algorithm.....
6173 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6174 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6175 if (rot_image.GetAlpha(ip, jp) > 64)
6176 rot_image.SetAlpha(ip, jp, 255);
6177
6178 wxBitmap os_bm(rot_image);
6179
6180 int w = os_bm.GetWidth();
6181 int h = os_bm.GetHeight();
6182 img_height = h;
6183
6184 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6185 lShipMidPoint.m_y - h / 2, true);
6186
6187 // Maintain dirty box,, missing in __WXMSW__ library
6188 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6189 lShipMidPoint.m_y - h / 2);
6190 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6191 lShipMidPoint.m_y - h / 2 + h);
6192 }
6193
6194 else if (g_OwnShipIconType == 2) { // Scaled Vector
6195 wxPoint ownship_icon[10];
6196
6197 for (int i = 0; i < 10; i++) {
6198 int j = i * 2;
6199 float pxa = (float)(s_ownship_icon[j]);
6200 float pya = (float)(s_ownship_icon[j + 1]);
6201 pya *= scale_factor_y;
6202 pxa *= scale_factor_x;
6203
6204 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6205 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6206
6207 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6208 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6209 }
6210
6211 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), 1, wxPENSTYLE_SOLID);
6212 dc.SetPen(ppPen1);
6213 dc.SetBrush(wxBrush(ShipColor()));
6214
6215 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6216
6217 // draw reference point (midships) cross
6218 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6219 ownship_icon[7].y);
6220 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6221 ownship_icon[9].y);
6222 }
6223
6224 img_height = ownShipLength * scale_factor_y;
6225
6226 // Reference point, where the GPS antenna is
6227 int circle_rad = 3;
6228 if (m_pos_image_user) circle_rad = 1;
6229
6230 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6231 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6232 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6233 } else { // Fixed bitmap icon.
6234 /* non opengl, or suboptimal opengl via ocpndc: */
6235 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6236 wxImage rot_image =
6237 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6238
6239 // Simple sharpening algorithm.....
6240 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6241 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6242 if (rot_image.GetAlpha(ip, jp) > 64)
6243 rot_image.SetAlpha(ip, jp, 255);
6244
6245 wxBitmap os_bm(rot_image);
6246
6247 if (g_ShipScaleFactorExp > 1) {
6248 wxImage scaled_image = os_bm.ConvertToImage();
6249 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6250 1.0; // soften the scale factor a bit
6251 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6252 scaled_image.GetHeight() * factor,
6253 wxIMAGE_QUALITY_HIGH));
6254 }
6255 int w = os_bm.GetWidth();
6256 int h = os_bm.GetHeight();
6257 img_height = h;
6258
6259 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6260 lShipMidPoint.m_y - h / 2, true);
6261
6262 // Reference point, where the GPS antenna is
6263 int circle_rad = 3;
6264 if (m_pos_image_user) circle_rad = 1;
6265
6266 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6267 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6268 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6269
6270 // Maintain dirty box,, missing in __WXMSW__ library
6271 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6272 lShipMidPoint.m_y - h / 2);
6273 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6274 lShipMidPoint.m_y - h / 2 + h);
6275 }
6276 } // ownship draw
6277 }
6278
6279 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6280}
6281
6282/* @ChartCanvas::CalcGridSpacing
6283 **
6284 ** Calculate the major and minor spacing between the lat/lon grid
6285 **
6286 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6287 *window
6288 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6289 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6290 ** @return [void]
6291 */
6292void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6293 float &MinorSpacing) {
6294 // table for calculating the distance between the grids
6295 // [0] view_scale ppm
6296 // [1] spacing between major grid lines in degrees
6297 // [2] spacing between minor grid lines in degrees
6298 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6299 {.000001f, 45.0f, 15.0f},
6300 {.0002f, 30.0f, 10.0f},
6301 {.0003f, 10.0f, 2.0f},
6302 {.0008f, 5.0f, 1.0f},
6303 {.001f, 2.0f, 30.0f / 60.0f},
6304 {.003f, 1.0f, 20.0f / 60.0f},
6305 {.006f, 0.5f, 10.0f / 60.0f},
6306 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6307 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6308 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6309 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6310 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6311 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6312 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6313 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6314
6315 unsigned int tabi;
6316 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6317 if (view_scale_ppm < lltab[tabi][0]) break;
6318 MajorSpacing = lltab[tabi][1]; // major latitude distance
6319 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6320 return;
6321}
6322/* @ChartCanvas::CalcGridText *************************************
6323 **
6324 ** Calculates text to display at the major grid lines
6325 **
6326 ** @param [r] latlon [float] latitude or longitude of grid line
6327 ** @param [r] spacing [float] distance between two major grid lines
6328 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6329 **
6330 ** @return
6331 */
6332
6333wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6334 int deg = (int)fabs(latlon); // degrees
6335 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6336 char postfix;
6337
6338 // calculate postfix letter (NSEW)
6339 if (latlon > 0.0) {
6340 if (bPostfix) {
6341 postfix = 'N';
6342 } else {
6343 postfix = 'E';
6344 }
6345 } else if (latlon < 0.0) {
6346 if (bPostfix) {
6347 postfix = 'S';
6348 } else {
6349 postfix = 'W';
6350 }
6351 } else {
6352 postfix = ' '; // no postfix for equator and greenwich
6353 }
6354 // calculate text, display minutes only if spacing is smaller than one degree
6355
6356 wxString ret;
6357 if (spacing >= 1.0) {
6358 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6359 } else if (spacing >= (1.0 / 60.0)) {
6360 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6361 } else {
6362 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6363 }
6364
6365 return ret;
6366}
6367
6368/* @ChartCanvas::GridDraw *****************************************
6369 **
6370 ** Draws major and minor Lat/Lon Grid on the chart
6371 ** - distance between Grid-lm ines are calculated automatic
6372 ** - major grid lines will be across the whole chart window
6373 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6374 **
6375 ** @param [w] dc [wxDC&] the wx drawing context
6376 **
6377 ** @return [void]
6378 ************************************************************************/
6379void ChartCanvas::GridDraw(ocpnDC &dc) {
6380 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6381
6382 double nlat, elon, slat, wlon;
6383 float lat, lon;
6384 float dlon;
6385 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6386 wxCoord w, h;
6387 wxPen GridPen(GetGlobalColor(_T ( "SNDG1" )), 1, wxPENSTYLE_SOLID);
6388 dc.SetPen(GridPen);
6389 if (!m_pgridFont) SetupGridFont();
6390 dc.SetFont(*m_pgridFont);
6391 dc.SetTextForeground(GetGlobalColor(_T ( "SNDG1" )));
6392
6393 w = m_canvas_width;
6394 h = m_canvas_height;
6395
6396 GetCanvasPixPoint(0, 0, nlat,
6397 wlon); // get lat/lon of upper left point of the window
6398 GetCanvasPixPoint(w, h, slat,
6399 elon); // get lat/lon of lower right point of the window
6400 dlon =
6401 elon -
6402 wlon; // calculate how many degrees of longitude are shown in the window
6403 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6404 {
6405 dlon = dlon + 360.0;
6406 }
6407 // calculate distance between latitude grid lines
6408 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6409
6410 // calculate position of first major latitude grid line
6411 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6412
6413 // Draw Major latitude grid lines and text
6414 while (lat < nlat) {
6415 wxPoint r;
6416 wxString st =
6417 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6418 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6419 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6420 dc.DrawText(st, 0, r.y); // draw text
6421 lat = lat + gridlatMajor;
6422
6423 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6424 }
6425
6426 // calculate position of first minor latitude grid line
6427 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6428
6429 // Draw minor latitude grid lines
6430 while (lat < nlat) {
6431 wxPoint r;
6432 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6433 dc.DrawLine(0, r.y, 10, r.y, false);
6434 dc.DrawLine(w - 10, r.y, w, r.y, false);
6435 lat = lat + gridlatMinor;
6436 }
6437
6438 // calculate distance between grid lines
6439 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6440
6441 // calculate position of first major latitude grid line
6442 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6443
6444 // draw major longitude grid lines
6445 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6446 wxPoint r;
6447 wxString st = CalcGridText(lon, gridlonMajor, false);
6448 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6449 dc.DrawLine(r.x, 0, r.x, h, false);
6450 dc.DrawText(st, r.x, 0);
6451 lon = lon + gridlonMajor;
6452 if (lon > 180.0) {
6453 lon = lon - 360.0;
6454 }
6455
6456 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6457 }
6458
6459 // calculate position of first minor longitude grid line
6460 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6461 // draw minor longitude grid lines
6462 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6463 wxPoint r;
6464 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6465 dc.DrawLine(r.x, 0, r.x, 10, false);
6466 dc.DrawLine(r.x, h - 10, r.x, h, false);
6467 lon = lon + gridlonMinor;
6468 if (lon > 180.0) {
6469 lon = lon - 360.0;
6470 }
6471 }
6472}
6473
6474void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6475 if (0 ) {
6476 double blat, blon, tlat, tlon;
6477 wxPoint r;
6478
6479 int x_origin = m_bDisplayGrid ? 60 : 20;
6480 int y_origin = m_canvas_height - 50;
6481
6482 float dist;
6483 int count;
6484 wxPen pen1, pen2;
6485
6486 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6487 {
6488 dist = 10.0;
6489 count = 5;
6490 pen1 = wxPen(GetGlobalColor(_T ( "SNDG2" )), 3, wxPENSTYLE_SOLID);
6491 pen2 = wxPen(GetGlobalColor(_T ( "SNDG1" )), 3, wxPENSTYLE_SOLID);
6492 } else // Draw 1 mile scale as SCALEB10
6493 {
6494 dist = 1.0;
6495 count = 10;
6496 pen1 = wxPen(GetGlobalColor(_T ( "SCLBR" )), 3, wxPENSTYLE_SOLID);
6497 pen2 = wxPen(GetGlobalColor(_T ( "CHGRD" )), 3, wxPENSTYLE_SOLID);
6498 }
6499
6500 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6501 double rotation = -VPoint.rotation;
6502
6503 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6504 GetCanvasPointPix(tlat, tlon, &r);
6505 int l1 = (y_origin - r.y) / count;
6506
6507 for (int i = 0; i < count; i++) {
6508 int y = l1 * i;
6509 if (i & 1)
6510 dc.SetPen(pen1);
6511 else
6512 dc.SetPen(pen2);
6513
6514 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6515 }
6516 } else {
6517 double blat, blon, tlat, tlon;
6518
6519 int x_origin = 5.0 * GetPixPerMM();
6520 int chartbar_height = GetChartbarHeight();
6521 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6522 // if (style->chartStatusWindowTransparent)
6523 // chartbar_height = 0;
6524 int y_origin = m_canvas_height - chartbar_height - 5;
6525#ifdef __WXOSX__
6526 if (!g_bopengl)
6527 y_origin =
6528 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6529#endif
6530
6531 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6532 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6533
6534 double d;
6535 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6536 d /= 2;
6537
6538 int unit = g_iDistanceFormat;
6539 if (d < .5 &&
6540 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6541 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6542
6543 // nice number
6544 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6545 float places = floor(logdist), rem = logdist - places;
6546 dist = pow(10, places);
6547
6548 if (rem < .2)
6549 dist /= 5;
6550 else if (rem < .5)
6551 dist /= 2;
6552
6553 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6554 wxPen pen1 = wxPen(GetGlobalColor(_T ( "UBLCK" )), 3, wxPENSTYLE_SOLID);
6555 double rotation = -VPoint.rotation;
6556
6557 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6558 &tlat, &tlon);
6559 wxPoint r;
6560 GetCanvasPointPix(tlat, tlon, &r);
6561 int l1 = r.x - x_origin;
6562
6563 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6564 12); // Store this for later reference
6565
6566 dc.SetPen(pen1);
6567
6568 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6569 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6570 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6571
6572 if (!m_pgridFont) SetupGridFont();
6573 dc.SetFont(*m_pgridFont);
6574 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
6575 int w, h;
6576 dc.GetTextExtent(s, &w, &h);
6577 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6578 if (g_bopengl) {
6579 w /= dpi_factor;
6580 h /= dpi_factor;
6581 }
6582 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6583 }
6584}
6585
6586void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6587 // Constants?
6588 double da_min = 2.;
6589 double da_max = 6.;
6590 double ra_min = 0.;
6591 double ra_max = 40.;
6592
6593 wxPen pen_save = dc.GetPen();
6594
6595 wxDateTime now = wxDateTime::Now();
6596
6597 dc.SetPen(pen);
6598
6599 int x0, y0, x1, y1;
6600
6601 x0 = x1 = x + radius; // Start point
6602 y0 = y1 = y;
6603 double angle = 0.;
6604 int i = 0;
6605
6606 while (angle < 360.) {
6607 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6608 angle += da;
6609
6610 if (angle > 360.) angle = 360.;
6611
6612 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6613
6614 double r;
6615 if (i & 1)
6616 r = radius + ra;
6617 else
6618 r = radius - ra;
6619
6620 x1 = (int)(x + cos(angle * PI / 180.) * r);
6621 y1 = (int)(y + sin(angle * PI / 180.) * r);
6622
6623 dc.DrawLine(x0, y0, x1, y1);
6624
6625 x0 = x1;
6626 y0 = y1;
6627
6628 i++;
6629 }
6630
6631 dc.DrawLine(x + radius, y, x1, y1); // closure
6632
6633 dc.SetPen(pen_save);
6634}
6635
6636static bool bAnchorSoundPlaying = false;
6637
6638static void onAnchorSoundFinished(void *ptr) {
6639 g_anchorwatch_sound->UnLoad();
6640 bAnchorSoundPlaying = false;
6641}
6642
6643void ChartCanvas::AlertDraw(ocpnDC &dc) {
6644 // Visual and audio alert for anchorwatch goes here
6645 bool play_sound = false;
6646 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6647 if (AnchorAlertOn1) {
6648 wxPoint TargetPoint;
6649 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
6650 &TargetPoint);
6651 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6652 TargetPoint.y, 100);
6653 play_sound = true;
6654 }
6655 } else
6656 AnchorAlertOn1 = false;
6657
6658 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6659 if (AnchorAlertOn2) {
6660 wxPoint TargetPoint;
6661 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
6662 &TargetPoint);
6663 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6664 TargetPoint.y, 100);
6665 play_sound = true;
6666 }
6667 } else
6668 AnchorAlertOn2 = false;
6669
6670 if (play_sound) {
6671 if (!bAnchorSoundPlaying) {
6672 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6673 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6674 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6675 if (g_anchorwatch_sound->IsOk()) {
6676 bAnchorSoundPlaying = true;
6677 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6678 g_anchorwatch_sound->Play();
6679 }
6680 }
6681 }
6682}
6683
6684void ChartCanvas::UpdateShips() {
6685 // Get the rectangle in the current dc which bounds the "ownship" symbol
6686
6687 wxClientDC dc(this);
6688 if (!dc.IsOk()) return;
6689
6690 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6691 if (!test_bitmap.IsOk()) return;
6692
6693 wxMemoryDC temp_dc(test_bitmap);
6694
6695 temp_dc.ResetBoundingBox();
6696 temp_dc.DestroyClippingRegion();
6697 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6698
6699 // Draw the ownship on the temp_dc
6700 ocpnDC ocpndc = ocpnDC(temp_dc);
6701 ShipDraw(ocpndc);
6702
6703 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6704 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6705 if (p) {
6706 wxPoint px;
6707 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6708 ocpndc.CalcBoundingBox(px.x, px.y);
6709 }
6710 }
6711
6712 ship_draw_rect =
6713 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6714 temp_dc.MaxY() - temp_dc.MinY());
6715
6716 wxRect own_ship_update_rect = ship_draw_rect;
6717
6718 if (!own_ship_update_rect.IsEmpty()) {
6719 // The required invalidate rectangle is the union of the last drawn
6720 // rectangle and this drawn rectangle
6721 own_ship_update_rect.Union(ship_draw_last_rect);
6722 own_ship_update_rect.Inflate(2);
6723 }
6724
6725 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6726
6727 ship_draw_last_rect = ship_draw_rect;
6728
6729 temp_dc.SelectObject(wxNullBitmap);
6730}
6731
6732void ChartCanvas::UpdateAlerts() {
6733 // Get the rectangle in the current dc which bounds the detected Alert
6734 // targets
6735
6736 // Use this dc
6737 wxClientDC dc(this);
6738
6739 // Get dc boundary
6740 int sx, sy;
6741 dc.GetSize(&sx, &sy);
6742
6743 // Need a bitmap
6744 wxBitmap test_bitmap(sx, sy, -1);
6745
6746 // Create a memory DC
6747 wxMemoryDC temp_dc;
6748 temp_dc.SelectObject(test_bitmap);
6749
6750 temp_dc.ResetBoundingBox();
6751 temp_dc.DestroyClippingRegion();
6752 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6753
6754 // Draw the Alert Targets on the temp_dc
6755 ocpnDC ocpndc = ocpnDC(temp_dc);
6756 AlertDraw(ocpndc);
6757
6758 // Retrieve the drawing extents
6759 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6760 temp_dc.MaxX() - temp_dc.MinX(),
6761 temp_dc.MaxY() - temp_dc.MinY());
6762
6763 if (!alert_rect.IsEmpty())
6764 alert_rect.Inflate(2); // clear all drawing artifacts
6765
6766 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6767 // The required invalidate rectangle is the union of the last drawn
6768 // rectangle and this drawn rectangle
6769 wxRect alert_update_rect = alert_draw_rect;
6770 alert_update_rect.Union(alert_rect);
6771
6772 // Invalidate the rectangular region
6773 RefreshRect(alert_update_rect, false);
6774 }
6775
6776 // Save this rectangle for next time
6777 alert_draw_rect = alert_rect;
6778
6779 temp_dc.SelectObject(wxNullBitmap); // clean up
6780}
6781
6782void ChartCanvas::UpdateAIS() {
6783 if (!g_pAIS) return;
6784
6785 // Get the rectangle in the current dc which bounds the detected AIS targets
6786
6787 // Use this dc
6788 wxClientDC dc(this);
6789
6790 // Get dc boundary
6791 int sx, sy;
6792 dc.GetSize(&sx, &sy);
6793
6794 wxRect ais_rect;
6795
6796 // How many targets are there?
6797
6798 // If more than "some number", it will be cheaper to refresh the entire
6799 // screen than to build update rectangles for each target.
6800 if (g_pAIS->GetTargetList().size() > 10) {
6801 ais_rect = wxRect(0, 0, sx, sy); // full screen
6802 } else {
6803 // Need a bitmap
6804 wxBitmap test_bitmap(sx, sy, -1);
6805
6806 // Create a memory DC
6807 wxMemoryDC temp_dc;
6808 temp_dc.SelectObject(test_bitmap);
6809
6810 temp_dc.ResetBoundingBox();
6811 temp_dc.DestroyClippingRegion();
6812 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6813
6814 // Draw the AIS Targets on the temp_dc
6815 ocpnDC ocpndc = ocpnDC(temp_dc);
6816 AISDraw(ocpndc, GetVP(), this);
6817 AISDrawAreaNotices(ocpndc, GetVP(), this);
6818
6819 // Retrieve the drawing extents
6820 ais_rect =
6821 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6822 temp_dc.MaxY() - temp_dc.MinY());
6823
6824 if (!ais_rect.IsEmpty())
6825 ais_rect.Inflate(2); // clear all drawing artifacts
6826
6827 temp_dc.SelectObject(wxNullBitmap); // clean up
6828 }
6829
6830 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6831 // The required invalidate rectangle is the union of the last drawn
6832 // rectangle and this drawn rectangle
6833 wxRect ais_update_rect = ais_draw_rect;
6834 ais_update_rect.Union(ais_rect);
6835
6836 // Invalidate the rectangular region
6837 RefreshRect(ais_update_rect, false);
6838 }
6839
6840 // Save this rectangle for next time
6841 ais_draw_rect = ais_rect;
6842}
6843
6844void ChartCanvas::ToggleCPAWarn() {
6845 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6846 wxString mess;
6847 if (g_bCPAWarn) {
6848 g_bTCPA_Max = true;
6849 mess = _("ON");
6850 } else {
6851 g_bTCPA_Max = false;
6852 mess = _("OFF");
6853 }
6854 // Print to status bar if available.
6855 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
6856 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
6857 } else {
6858 if (!g_AisFirstTimeUse) {
6859 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
6860 _("CPA") + " " + mess, 4, 4);
6861 }
6862 }
6863}
6864
6865void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
6866
6867void ChartCanvas::OnSize(wxSizeEvent &event) {
6868 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
6869 // GetClientSize returns the size of the canvas area in logical pixels.
6870 GetClientSize(&m_canvas_width, &m_canvas_height);
6871
6872#ifdef __WXOSX__
6873 // Support scaled HDPI displays.
6874 m_displayScale = GetContentScaleFactor();
6875#endif
6876
6877 // Convert to physical pixels.
6878 m_canvas_width *= m_displayScale;
6879 m_canvas_height *= m_displayScale;
6880
6881 // Resize the current viewport
6882 VPoint.pix_width = m_canvas_width;
6883 VPoint.pix_height = m_canvas_height;
6884 VPoint.SetPixelScale(m_displayScale);
6885
6886 // Get some canvas metrics
6887
6888 // Rescale to current value, in order to rebuild VPoint data
6889 // structures for new canvas size
6891
6892 m_absolute_min_scale_ppm =
6893 m_canvas_width /
6894 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
6895
6896 // Inform the parent Frame that I am being resized...
6897 gFrame->ProcessCanvasResize();
6898
6899 // if MUIBar is active, size the bar
6900 // if(g_useMUI && !m_muiBar){ // rebuild if
6901 // necessary
6902 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
6903 // m_muiBarHOSize = m_muiBar->GetSize();
6904 // }
6905
6906 if (m_muiBar) {
6907 SetMUIBarPosition();
6908 UpdateFollowButtonState();
6909 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
6910 }
6911
6912 // Set up the scroll margins
6913 xr_margin = m_canvas_width * 95 / 100;
6914 xl_margin = m_canvas_width * 5 / 100;
6915 yt_margin = m_canvas_height * 5 / 100;
6916 yb_margin = m_canvas_height * 95 / 100;
6917
6918 if (m_pQuilt)
6919 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
6920
6921 // Resize the scratch BM
6922 delete pscratch_bm;
6923 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
6924 m_brepaint_piano = true;
6925
6926 // Resize the Route Calculation BM
6927 m_dc_route.SelectObject(wxNullBitmap);
6928 delete proute_bm;
6929 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
6930 m_dc_route.SelectObject(*proute_bm);
6931
6932 // Resize the saved Bitmap
6933 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
6934
6935 // Resize the working Bitmap
6936 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
6937
6938 // Rescale again, to capture all the changes for new canvas size
6940
6941#ifdef ocpnUSE_GL
6942 if (/*g_bopengl &&*/ m_glcc) {
6943 // FIXME (dave) This can go away?
6944 m_glcc->OnSize(event);
6945 }
6946#endif
6947
6948 FormatPianoKeys();
6949 // Invalidate the whole window
6950 ReloadVP();
6951}
6952
6953void ChartCanvas::ProcessNewGUIScale() {
6954 // m_muiBar->Hide();
6955 delete m_muiBar;
6956 m_muiBar = 0;
6957
6958 CreateMUIBar();
6959}
6960
6961void ChartCanvas::CreateMUIBar() {
6962 if (g_useMUI && !m_muiBar) { // rebuild if necessary
6963
6964 // We need to update the m_bENCGroup flag, at least for the initial creation
6965 // of a MUIBar
6966 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
6967
6968 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
6969 m_muiBar->SetColorScheme(m_cs);
6970 m_muiBarHOSize = m_muiBar->m_size;
6971 }
6972
6973 if (m_muiBar) {
6974 SetMUIBarPosition();
6975 UpdateFollowButtonState();
6976 m_muiBar->UpdateDynamicValues();
6977 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
6978 }
6979}
6980
6981void ChartCanvas::SetMUIBarPosition() {
6982 // if MUIBar is active, size the bar
6983 if (m_muiBar) {
6984 // We estimate the piano width based on the canvas width
6985 int pianoWidth = GetClientSize().x * 0.6f;
6986 // If the piano already exists, we can use its exact width
6987 // if(m_Piano)
6988 // pianoWidth = m_Piano->GetWidth();
6989
6990 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
6991 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
6992 delete m_muiBar;
6993 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
6994 m_muiBar->SetColorScheme(m_cs);
6995 }
6996 }
6997
6998 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
6999 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7000 delete m_muiBar;
7001 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7002 m_muiBar->SetColorScheme(m_cs);
7003 }
7004 }
7005
7006 m_muiBar->SetBestPosition();
7007 }
7008}
7009
7010void ChartCanvas::DestroyMuiBar() {
7011 if (m_muiBar) {
7012 delete m_muiBar;
7013 m_muiBar = NULL;
7014 }
7015}
7016
7017void ChartCanvas::ShowCompositeInfoWindow(
7018 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7019 if (n_charts > 0) {
7020 if (NULL == m_pCIWin) {
7021 m_pCIWin = new ChInfoWin(this);
7022 m_pCIWin->Hide();
7023 }
7024
7025 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7026 wxString s;
7027
7028 s = _("Composite of ");
7029
7030 wxString s1;
7031 s1.Printf("%d ", n_charts);
7032 if (n_charts > 1)
7033 s1 += _("charts");
7034 else
7035 s1 += _("chart");
7036 s += s1;
7037 s += '\n';
7038
7039 s1.Printf(_("Chart scale"));
7040 s1 += ": ";
7041 wxString s2;
7042 s2.Printf("1:%d\n", scale);
7043 s += s1;
7044 s += s2;
7045
7046 s1 = _("Zoom in for more information");
7047 s += s1;
7048 s += '\n';
7049
7050 int char_width = s1.Length();
7051 int char_height = 3;
7052
7053 if (g_bChartBarEx) {
7054 s += '\n';
7055 int j = 0;
7056 for (int i : index_vector) {
7057 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7058 wxString path = cte.GetFullSystemPath();
7059 s += path;
7060 s += '\n';
7061 char_height++;
7062 char_width = wxMax(char_width, path.Length());
7063 if (j++ >= 9) break;
7064 }
7065 if (j >= 9) {
7066 s += " .\n .\n .\n";
7067 char_height += 3;
7068 }
7069 s += '\n';
7070 char_height += 1;
7071
7072 char_width += 4; // Fluff
7073 }
7074
7075 m_pCIWin->SetString(s);
7076
7077 m_pCIWin->FitToChars(char_width, char_height);
7078
7079 wxPoint p;
7080 p.x = x / GetContentScaleFactor();
7081 if ((p.x + m_pCIWin->GetWinSize().x) >
7082 (m_canvas_width / GetContentScaleFactor()))
7083 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7084 m_pCIWin->GetWinSize().x) /
7085 2; // centered
7086
7087 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7088 4 - m_pCIWin->GetWinSize().y;
7089
7090 m_pCIWin->dbIndex = 0;
7091 m_pCIWin->chart_scale = 0;
7092 m_pCIWin->SetPosition(p);
7093 m_pCIWin->SetBitmap();
7094 m_pCIWin->Refresh();
7095 m_pCIWin->Show();
7096 }
7097 } else {
7098 HideChartInfoWindow();
7099 }
7100}
7101
7102void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7103 if (dbIndex >= 0) {
7104 if (NULL == m_pCIWin) {
7105 m_pCIWin = new ChInfoWin(this);
7106 m_pCIWin->Hide();
7107 }
7108
7109 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7110 wxString s;
7111 ChartBase *pc = NULL;
7112
7113 // TOCTOU race but worst case will reload chart.
7114 // need to lock it or the background spooler may evict charts in
7115 // OpenChartFromDBAndLock
7116 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7117 pc = ChartData->OpenChartFromDBAndLock(
7118 dbIndex, FULL_INIT); // this must come from cache
7119
7120 int char_width, char_height;
7121 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7122 if (pc) ChartData->UnLockCacheChart(dbIndex);
7123
7124 m_pCIWin->SetString(s);
7125 m_pCIWin->FitToChars(char_width, char_height);
7126
7127 wxPoint p;
7128 p.x = x / GetContentScaleFactor();
7129 if ((p.x + m_pCIWin->GetWinSize().x) >
7130 (m_canvas_width / GetContentScaleFactor()))
7131 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7132 m_pCIWin->GetWinSize().x) /
7133 2; // centered
7134
7135 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7136 4 - m_pCIWin->GetWinSize().y;
7137
7138 m_pCIWin->dbIndex = dbIndex;
7139 m_pCIWin->SetPosition(p);
7140 m_pCIWin->SetBitmap();
7141 m_pCIWin->Refresh();
7142 m_pCIWin->Show();
7143 }
7144 } else {
7145 HideChartInfoWindow();
7146 }
7147}
7148
7149void ChartCanvas::HideChartInfoWindow(void) {
7150 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7151 m_pCIWin->Hide();
7152 m_pCIWin->Destroy();
7153 m_pCIWin = NULL;
7154
7155#ifdef __ANDROID__
7156 androidForceFullRepaint();
7157#endif
7158 }
7159}
7160
7161void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7162 wxMouseEvent ev(wxEVT_MOTION);
7163 ev.m_x = mouse_x;
7164 ev.m_y = mouse_y;
7165 ev.m_leftDown = mouse_leftisdown;
7166
7167 wxEvtHandler *evthp = GetEventHandler();
7168
7169 ::wxPostEvent(evthp, ev);
7170}
7171
7172void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7173 if ((m_panx_target_final - m_panx_target_now) ||
7174 (m_pany_target_final - m_pany_target_now)) {
7175 DoTimedMovementTarget();
7176 } else
7177 DoTimedMovement();
7178}
7179
7180void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7181
7182bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7183 int delta) {
7184 if (m_disable_edge_pan) return false;
7185
7186 bool bft = false;
7187 int pan_margin = m_canvas_width * margin / 100;
7188 int pan_timer_set = 200;
7189 double pan_delta = GetVP().pix_width * delta / 100;
7190 int pan_x = 0;
7191 int pan_y = 0;
7192
7193 if (x > m_canvas_width - pan_margin) {
7194 bft = true;
7195 pan_x = pan_delta;
7196 }
7197
7198 else if (x < pan_margin) {
7199 bft = true;
7200 pan_x = -pan_delta;
7201 }
7202
7203 if (y < pan_margin) {
7204 bft = true;
7205 pan_y = -pan_delta;
7206 }
7207
7208 else if (y > m_canvas_height - pan_margin) {
7209 bft = true;
7210 pan_y = pan_delta;
7211 }
7212
7213 // Of course, if dragging, and the mouse left button is not down, we must
7214 // stop the event injection
7215 if (bdragging) {
7216 if (!g_btouch) {
7217 wxMouseState state = ::wxGetMouseState();
7218#if wxCHECK_VERSION(3, 0, 0)
7219 if (!state.LeftIsDown())
7220#else
7221 if (!state.LeftDown())
7222#endif
7223 bft = false;
7224 }
7225 }
7226 if ((bft) && !pPanTimer->IsRunning()) {
7227 PanCanvas(pan_x, pan_y);
7228 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7229 return true;
7230 }
7231
7232 // This mouse event must not be due to pan timer event injector
7233 // Mouse is out of the pan zone, so prevent any orphan event injection
7234 if ((!bft) && pPanTimer->IsRunning()) {
7235 pPanTimer->Stop();
7236 }
7237
7238 return (false);
7239}
7240
7241// Look for waypoints at the current position.
7242// Used to determine what a mouse event should act on.
7243
7244void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7245 bool setBeingEdited) {
7246 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7247 m_pRoutePointEditTarget = NULL;
7248 m_pFoundPoint = NULL;
7249
7250 SelectItem *pFind = NULL;
7251 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7252 SelectableItemList SelList = pSelect->FindSelectionList(
7253 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7254 wxSelectableItemListNode *node = SelList.GetFirst();
7255 while (node) {
7256 pFind = node->GetData();
7257
7258 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7259
7260 // Get an array of all routes using this point
7261 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7262 // TODO: delete m_pEditRouteArray after use?
7263
7264 // Use route array to determine actual visibility for the point
7265 bool brp_viz = false;
7266 if (m_pEditRouteArray) {
7267 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7268 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7269 if (pr->IsVisible()) {
7270 brp_viz = true;
7271 break;
7272 }
7273 }
7274 } else
7275 brp_viz = frp->IsVisible(); // isolated point
7276
7277 if (brp_viz) {
7278 // Use route array to rubberband all affected routes
7279 if (m_pEditRouteArray) // Editing Waypoint as part of route
7280 {
7281 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7282 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7283 pr->m_bIsBeingEdited = setBeingEdited;
7284 }
7285 m_bRouteEditing = setBeingEdited;
7286 } else // editing Mark
7287 {
7288 frp->m_bRPIsBeingEdited = setBeingEdited;
7289 m_bMarkEditing = setBeingEdited;
7290 }
7291
7292 m_pRoutePointEditTarget = frp;
7293 m_pFoundPoint = pFind;
7294 break; // out of the while(node)
7295 }
7296
7297 node = node->GetNext();
7298 } // while (node)
7299}
7300std::shared_ptr<PI_PointContext> ChartCanvas::GetCanvasContextAtPoint(int x,
7301 int y) {
7302 // General Right Click
7303 // Look for selectable objects
7304 double slat, slon;
7305 GetCanvasPixPoint(x, y, slat, slon);
7306
7307 SelectItem *pFindAIS;
7308 SelectItem *pFindRP;
7309 SelectItem *pFindRouteSeg;
7310 SelectItem *pFindTrackSeg;
7311 SelectItem *pFindCurrent = NULL;
7312 SelectItem *pFindTide = NULL;
7313
7314 // Get all the selectable things at the selected point
7315 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7316 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7317 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7318 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7319 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7320
7321 if (m_bShowCurrent)
7322 pFindCurrent =
7323 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7324
7325 if (m_bShowTide) // look for tide stations
7326 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7327
7328 int seltype = 0;
7329
7330 // Try for AIS targets first
7331 int FoundAIS_MMSI = 0;
7332 if (pFindAIS) {
7333 FoundAIS_MMSI = pFindAIS->GetUserData();
7334
7335 // Make sure the target data is available
7336 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7337 seltype |= SELTYPE_AISTARGET;
7338 }
7339
7340 // Now the various Route Parts
7341
7342 RoutePoint *FoundRoutePoint = NULL;
7343 Route *SelectedRoute = NULL;
7344
7345 if (pFindRP) {
7346 RoutePoint *pFirstVizPoint = NULL;
7347 RoutePoint *pFoundActiveRoutePoint = NULL;
7348 RoutePoint *pFoundVizRoutePoint = NULL;
7349 Route *pSelectedActiveRoute = NULL;
7350 Route *pSelectedVizRoute = NULL;
7351
7352 // There is at least one routepoint, so get the whole list
7353 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7354 SelectableItemList SelList =
7355 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7356 wxSelectableItemListNode *node = SelList.GetFirst();
7357 while (node) {
7358 SelectItem *pFindSel = node->GetData();
7359
7360 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7361
7362 // Get an array of all routes using this point
7363 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7364
7365 // Use route array (if any) to determine actual visibility for this point
7366 bool brp_viz = false;
7367 if (proute_array) {
7368 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7369 Route *pr = (Route *)proute_array->Item(ir);
7370 if (pr->IsVisible()) {
7371 brp_viz = true;
7372 break;
7373 }
7374 }
7375 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7376 // but still exists as a waypoint
7377 brp_viz = prp->IsVisible(); // so treat as isolated point
7378
7379 } else
7380 brp_viz = prp->IsVisible(); // isolated point
7381
7382 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7383
7384 // Use route array to choose the appropriate route
7385 // Give preference to any active route, otherwise select the first visible
7386 // route in the array for this point
7387 if (proute_array) {
7388 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7389 Route *pr = (Route *)proute_array->Item(ir);
7390 if (pr->m_bRtIsActive) {
7391 pSelectedActiveRoute = pr;
7392 pFoundActiveRoutePoint = prp;
7393 break;
7394 }
7395 }
7396
7397 if (NULL == pSelectedVizRoute) {
7398 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7399 Route *pr = (Route *)proute_array->Item(ir);
7400 if (pr->IsVisible()) {
7401 pSelectedVizRoute = pr;
7402 pFoundVizRoutePoint = prp;
7403 break;
7404 }
7405 }
7406 }
7407
7408 delete proute_array;
7409 }
7410
7411 node = node->GetNext();
7412 }
7413
7414 // Now choose the "best" selections
7415 if (pFoundActiveRoutePoint) {
7416 FoundRoutePoint = pFoundActiveRoutePoint;
7417 SelectedRoute = pSelectedActiveRoute;
7418 } else if (pFoundVizRoutePoint) {
7419 FoundRoutePoint = pFoundVizRoutePoint;
7420 SelectedRoute = pSelectedVizRoute;
7421 } else
7422 // default is first visible point in list
7423 FoundRoutePoint = pFirstVizPoint;
7424
7425 if (SelectedRoute) {
7426 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7427 } else if (FoundRoutePoint) {
7428 seltype |= SELTYPE_MARKPOINT;
7429 }
7430
7431 // Highlight the selected point, to verify the proper right click selection
7432#if 0
7433 if (m_pFoundRoutePoint) {
7434 m_pFoundRoutePoint->m_bPtIsSelected = true;
7435 wxRect wp_rect;
7436 RoutePointGui(*m_pFoundRoutePoint)
7437 .CalculateDCRect(m_dc_route, this, &wp_rect);
7438 RefreshRect(wp_rect, true);
7439 }
7440#endif
7441 }
7442
7443 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7444 // routes But call the popup handler with identifier appropriate to the type
7445 if (pFindRouteSeg) // there is at least one select item
7446 {
7447 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7448 SelectableItemList SelList =
7449 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7450
7451 if (NULL == SelectedRoute) // the case where a segment only is selected
7452 {
7453 // Choose the first visible route containing segment in the list
7454 wxSelectableItemListNode *node = SelList.GetFirst();
7455 while (node) {
7456 SelectItem *pFindSel = node->GetData();
7457
7458 Route *pr = (Route *)pFindSel->m_pData3;
7459 if (pr->IsVisible()) {
7460 SelectedRoute = pr;
7461 break;
7462 }
7463 node = node->GetNext();
7464 }
7465 }
7466
7467 if (SelectedRoute) {
7468 if (NULL == FoundRoutePoint)
7469 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7470
7471 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7472 seltype |= SELTYPE_ROUTESEGMENT;
7473 }
7474 }
7475
7476#if 0
7477 if (pFindTrackSeg) {
7478 m_pSelectedTrack = NULL;
7479 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7480 SelectableItemList SelList =
7481 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7482
7483 // Choose the first visible track containing segment in the list
7484 wxSelectableItemListNode *node = SelList.GetFirst();
7485 while (node) {
7486 SelectItem *pFindSel = node->GetData();
7487
7488 Track *pt = (Track *)pFindSel->m_pData3;
7489 if (pt->IsVisible()) {
7490 m_pSelectedTrack = pt;
7491 break;
7492 }
7493 node = node->GetNext();
7494 }
7495
7496 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7497 }
7498#endif
7499
7500#if 0
7501 bool bseltc = false;
7502 // if(0 == seltype)
7503 {
7504 if (pFindCurrent) {
7505 // There may be multiple current entries at the same point.
7506 // For example, there often is a current substation (with directions
7507 // specified) co-located with its master. We want to select the
7508 // substation, so that the direction will be properly indicated on the
7509 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
7510 // substation)
7511 IDX_entry *pIDX_best_candidate;
7512
7513 SelectItem *pFind = NULL;
7514 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7515 SelectableItemList SelList = pSelectTC->FindSelectionList(
7516 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_CURRENTPOINT);
7517
7518 // Default is first entry
7519 wxSelectableItemListNode *node = SelList.GetFirst();
7520 pFind = node->GetData();
7521 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7522
7523 if (SelList.GetCount() > 1) {
7524 node = node->GetNext();
7525 while (node) {
7526 pFind = node->GetData();
7527 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
7528 if (pIDX_candidate->IDX_type == 'c') {
7529 pIDX_best_candidate = pIDX_candidate;
7530 break;
7531 }
7532
7533 node = node->GetNext();
7534 } // while (node)
7535 } else {
7536 wxSelectableItemListNode *node = SelList.GetFirst();
7537 pFind = node->GetData();
7538 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7539 }
7540
7541 m_pIDXCandidate = pIDX_best_candidate;
7542
7543 if (0 == seltype) {
7544 DrawTCWindow(x, y, (void *)pIDX_best_candidate);
7545 Refresh(false);
7546 bseltc = true;
7547 } else
7548 seltype |= SELTYPE_CURRENTPOINT;
7549 }
7550
7551 else if (pFindTide) {
7552 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
7553
7554 if (0 == seltype) {
7555 DrawTCWindow(x, y, (void *)pFindTide->m_pData1);
7556 Refresh(false);
7557 bseltc = true;
7558 } else
7559 seltype |= SELTYPE_TIDEPOINT;
7560 }
7561 }
7562#endif
7563
7564 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7565
7566 // Populate the return struct
7567 auto rstruct = std::make_shared<PI_PointContext>();
7568 rstruct->object_type = OBJECT_CHART;
7569 rstruct->object_ident = "";
7570
7571 if (seltype == SELTYPE_AISTARGET) {
7572 rstruct->object_type = OBJECT_AISTARGET;
7573 wxString val;
7574 val.Printf("%d", FoundAIS_MMSI);
7575 rstruct->object_ident = val.ToStdString();
7576 } else if (seltype & SELTYPE_MARKPOINT) {
7577 if (FoundRoutePoint) {
7578 rstruct->object_type = OBJECT_ROUTEPOINT;
7579 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7580 }
7581 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7582 if (SelectedRoute) {
7583 rstruct->object_type = OBJECT_ROUTESEGMENT;
7584 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7585 }
7586 }
7587
7588 return rstruct;
7589}
7590
7591void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7592 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7593 singleClickEventIsValid = false;
7594 m_DoubleClickTimer->Stop();
7595}
7596
7597bool leftIsDown;
7598
7599bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7600 if (!m_bChartDragging && !m_bDrawingRoute) {
7601 /*
7602 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7603 * mouse event coordinates are in logical pixels.
7604 */
7605 if (m_Compass && m_Compass->IsShown()) {
7606 wxRect logicalRect = m_Compass->GetLogicalRect();
7607 bool isInCompass = logicalRect.Contains(event.GetPosition());
7608 if (isInCompass) {
7609 if (m_Compass->MouseEvent(event)) {
7610 cursor_region = CENTER;
7611 if (!g_btouch) SetCanvasCursor(event);
7612 return true;
7613 }
7614 }
7615 }
7616
7617 if (m_notification_button && m_notification_button->IsShown()) {
7618 wxRect logicalRect = m_notification_button->GetLogicalRect();
7619 bool isinButton = logicalRect.Contains(event.GetPosition());
7620 if (isinButton) {
7621 SetCursor(*pCursorArrow);
7622 if (event.LeftDown()) HandleNotificationMouseClick();
7623 return true;
7624 }
7625 }
7626
7627 if (MouseEventToolbar(event)) return true;
7628
7629 if (MouseEventChartBar(event)) return true;
7630
7631 if (MouseEventMUIBar(event)) return true;
7632
7633 if (MouseEventIENCBar(event)) return true;
7634 }
7635 return false;
7636}
7637
7638void ChartCanvas::HandleNotificationMouseClick() {
7639 if (!m_NotificationsList) {
7640 m_NotificationsList = new NotificationsList(this);
7641
7642 // calculate best size for Notification list
7643 m_NotificationsList->RecalculateSize();
7644 m_NotificationsList->Hide();
7645 }
7646
7647 if (m_NotificationsList->IsShown()) {
7648 m_NotificationsList->Hide();
7649 } else {
7650 m_NotificationsList->RecalculateSize();
7651 m_NotificationsList->ReloadNotificationList();
7652 m_NotificationsList->Show();
7653 }
7654}
7655bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7656 if (!g_bShowChartBar) return false;
7657
7658 if (!m_Piano->MouseEvent(event)) return false;
7659
7660 cursor_region = CENTER;
7661 if (!g_btouch) SetCanvasCursor(event);
7662 return true;
7663}
7664
7665bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7666 if (!IsPrimaryCanvas()) return false;
7667
7668 if (g_MainToolbar) {
7669 if (!g_MainToolbar->MouseEvent(event))
7670 return false;
7671 else
7672 g_MainToolbar->RefreshToolbar();
7673 }
7674
7675 cursor_region = CENTER;
7676 if (!g_btouch) SetCanvasCursor(event);
7677 return true;
7678}
7679
7680bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7681 if (!IsPrimaryCanvas()) return false;
7682
7683 if (g_iENCToolbar) {
7684 if (!g_iENCToolbar->MouseEvent(event))
7685 return false;
7686 else {
7687 g_iENCToolbar->RefreshToolbar();
7688 return true;
7689 }
7690 }
7691 return false;
7692}
7693
7694bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7695 if (m_muiBar) {
7696 if (!m_muiBar->MouseEvent(event)) return false;
7697 }
7698
7699 cursor_region = CENTER;
7700 if (!g_btouch) SetCanvasCursor(event);
7701 if (m_muiBar)
7702 return true;
7703 else
7704 return false;
7705}
7706
7707bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7708 int x, y;
7709
7710 bool bret = false;
7711
7712 event.GetPosition(&x, &y);
7713
7714 x *= m_displayScale;
7715 y *= m_displayScale;
7716
7717 m_MouseDragging = event.Dragging();
7718
7719 // Some systems produce null drag events, where the pointer position has not
7720 // changed from the previous value. Detect this case, and abort further
7721 // processing (FS#1748)
7722#ifdef __WXMSW__
7723 if (event.Dragging()) {
7724 if ((x == mouse_x) && (y == mouse_y)) return true;
7725 }
7726#endif
7727
7728 mouse_x = x;
7729 mouse_y = y;
7730 mouse_leftisdown = event.LeftDown();
7732
7733 // Establish the event region
7734 cursor_region = CENTER;
7735
7736 int chartbar_height = GetChartbarHeight();
7737
7738 if (m_Compass && m_Compass->IsShown() &&
7739 m_Compass->GetRect().Contains(event.GetPosition())) {
7740 cursor_region = CENTER;
7741 } else if (x > xr_margin) {
7742 cursor_region = MID_RIGHT;
7743 } else if (x < xl_margin) {
7744 cursor_region = MID_LEFT;
7745 } else if (y > yb_margin - chartbar_height &&
7746 y < m_canvas_height - chartbar_height) {
7747 cursor_region = MID_TOP;
7748 } else if (y < yt_margin) {
7749 cursor_region = MID_BOT;
7750 } else {
7751 cursor_region = CENTER;
7752 }
7753
7754 if (!g_btouch) SetCanvasCursor(event);
7755
7756 // Protect from leftUp's coming from event handlers in child
7757 // windows who return focus to the canvas.
7758 leftIsDown = event.LeftDown();
7759
7760#ifndef __WXOSX__
7761 if (event.LeftDown()) {
7762 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7763 // The menu bar is temporarily visible due to alt having been pressed.
7764 // Clicking will hide it, and do nothing else.
7765 g_bTempShowMenuBar = false;
7766 parent_frame->ApplyGlobalSettings(false);
7767 return (true);
7768 }
7769 }
7770#endif
7771
7772 // Update modifiers here; some window managers never send the key event
7773 m_modkeys = 0;
7774 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7775 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7776
7777#ifdef __WXMSW__
7778 // TODO Test carefully in other platforms, remove ifdef....
7779 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7780 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7781#endif
7782
7783 event.SetEventObject(this);
7784 if (SendMouseEventToPlugins(event))
7785 return (true); // PlugIn did something, and does not want the canvas to
7786 // do anything else
7787
7788 // Capture LeftUp's and time them, unless it already came from the timer.
7789
7790 // Detect end of chart dragging
7791 if (g_btouch && m_bChartDragging && event.LeftUp()) {
7792 StartChartDragInertia();
7793 }
7794
7795 if (b_handle_dclick && event.LeftUp() && !singleClickEventIsValid) {
7796 // Ignore the second LeftUp after the DClick.
7797 if (m_DoubleClickTimer->IsRunning()) {
7798 m_DoubleClickTimer->Stop();
7799 return (true);
7800 }
7801
7802 // Save the event for later running if there is no DClick.
7803 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7804 singleClickEvent = event;
7805 singleClickEventIsValid = true;
7806 return (true);
7807 }
7808
7809 // This logic is necessary on MSW to handle the case where
7810 // a context (right-click) menu is dismissed without action
7811 // by clicking on the chart surface.
7812 // We need to avoid an unintentional pan by eating some clicks...
7813#ifdef __WXMSW__
7814 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7815 if (g_click_stop > 0) {
7816 g_click_stop--;
7817 return (true);
7818 }
7819 }
7820#endif
7821
7822 // Kick off the Rotation control timer
7823 if (GetUpMode() == COURSE_UP_MODE) {
7824 m_b_rot_hidef = false;
7825 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7826 } else
7827 pRotDefTimer->Stop();
7828
7829 // Retrigger the route leg / AIS target popup timer
7830 bool bRoll = !g_btouch;
7831#ifdef __ANDROID__
7832 bRoll = g_bRollover;
7833#endif
7834 if (bRoll) {
7835 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7836 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7837 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7838 m_RolloverPopupTimer.Start(
7839 10,
7840 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7841 else
7842 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7843 }
7844
7845 // Retrigger the cursor tracking timer
7846 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7847
7848// Show cursor position on Status Bar, if present
7849// except for GTK, under which status bar updates are very slow
7850// due to Update() call.
7851// In this case, as a workaround, update the status window
7852// after an interval timer (pCurTrackTimer) pops, which will happen
7853// whenever the mouse has stopped moving for specified interval.
7854// See the method OnCursorTrackTimerEvent()
7855#if !defined(__WXGTK__) && !defined(__WXQT__)
7856 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7857#endif
7858
7859 // Send the current cursor lat/lon to all PlugIns requesting it
7860 if (g_pi_manager) {
7861 // Occasionally, MSW will produce nonsense events on right click....
7862 // This results in an error in cursor geo position, so we skip this case
7863 if ((x >= 0) && (y >= 0))
7864 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7865 }
7866
7867 if (!g_btouch) {
7868 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7869 wxPoint p = ClientToScreen(wxPoint(x, y));
7870 }
7871 }
7872
7873 if (1 ) {
7874 // Route Creation Rubber Banding
7875 if (m_routeState >= 2) {
7876 r_rband.x = x;
7877 r_rband.y = y;
7878 m_bDrawingRoute = true;
7879
7880 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7881 Refresh(false);
7882 }
7883
7884 // Measure Tool Rubber Banding
7885 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7886 r_rband.x = x;
7887 r_rband.y = y;
7888 m_bDrawingRoute = true;
7889
7890 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7891 Refresh(false);
7892 }
7893 }
7894 return bret;
7895}
7896
7897int ChartCanvas::PrepareContextSelections(double lat, double lon) {
7898 // On general Right Click
7899 // Look for selectable objects
7900 double slat = lat;
7901 double slon = lon;
7902
7903#if defined(__WXMAC__) || defined(__ANDROID__)
7904 wxScreenDC sdc;
7905 ocpnDC dc(sdc);
7906#else
7907 wxClientDC cdc(GetParent());
7908 ocpnDC dc(cdc);
7909#endif
7910
7911 SelectItem *pFindAIS;
7912 SelectItem *pFindRP;
7913 SelectItem *pFindRouteSeg;
7914 SelectItem *pFindTrackSeg;
7915 SelectItem *pFindCurrent = NULL;
7916 SelectItem *pFindTide = NULL;
7917
7918 // Deselect any current objects
7919 if (m_pSelectedRoute) {
7920 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7921 m_pSelectedRoute->DeSelectRoute();
7922#ifdef ocpnUSE_GL
7923 if (g_bopengl && m_glcc) {
7924 InvalidateGL();
7925 Update();
7926 } else
7927#endif
7928 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7929 }
7930
7931 if (m_pFoundRoutePoint) {
7932 m_pFoundRoutePoint->m_bPtIsSelected = false;
7933 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
7934 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
7935 }
7936
7939 if (g_btouch && m_pRoutePointEditTarget) {
7940 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7941 m_pRoutePointEditTarget->m_bPtIsSelected = false;
7942 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7943 }
7944
7945 // Get all the selectable things at the cursor
7946 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7947 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7948 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7949 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7950 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7951
7952 if (m_bShowCurrent)
7953 pFindCurrent =
7954 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7955
7956 if (m_bShowTide) // look for tide stations
7957 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7958
7959 int seltype = 0;
7960
7961 // Try for AIS targets first
7962 if (pFindAIS) {
7963 m_FoundAIS_MMSI = pFindAIS->GetUserData();
7964
7965 // Make sure the target data is available
7966 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
7967 seltype |= SELTYPE_AISTARGET;
7968 }
7969
7970 // Now examine the various Route parts
7971
7972 m_pFoundRoutePoint = NULL;
7973 if (pFindRP) {
7974 RoutePoint *pFirstVizPoint = NULL;
7975 RoutePoint *pFoundActiveRoutePoint = NULL;
7976 RoutePoint *pFoundVizRoutePoint = NULL;
7977 Route *pSelectedActiveRoute = NULL;
7978 Route *pSelectedVizRoute = NULL;
7979
7980 // There is at least one routepoint, so get the whole list
7981 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7982 SelectableItemList SelList =
7983 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7984 wxSelectableItemListNode *node = SelList.GetFirst();
7985 while (node) {
7986 SelectItem *pFindSel = node->GetData();
7987
7988 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7989
7990 // Get an array of all routes using this point
7991 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7992
7993 // Use route array (if any) to determine actual visibility for this point
7994 bool brp_viz = false;
7995 if (proute_array) {
7996 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7997 Route *pr = (Route *)proute_array->Item(ir);
7998 if (pr->IsVisible()) {
7999 brp_viz = true;
8000 break;
8001 }
8002 }
8003 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8004 // but still exists as a waypoint
8005 brp_viz = prp->IsVisible(); // so treat as isolated point
8006
8007 } else
8008 brp_viz = prp->IsVisible(); // isolated point
8009
8010 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8011
8012 // Use route array to choose the appropriate route
8013 // Give preference to any active route, otherwise select the first visible
8014 // route in the array for this point
8015 m_pSelectedRoute = NULL;
8016 if (proute_array) {
8017 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8018 Route *pr = (Route *)proute_array->Item(ir);
8019 if (pr->m_bRtIsActive) {
8020 pSelectedActiveRoute = pr;
8021 pFoundActiveRoutePoint = prp;
8022 break;
8023 }
8024 }
8025
8026 if (NULL == pSelectedVizRoute) {
8027 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8028 Route *pr = (Route *)proute_array->Item(ir);
8029 if (pr->IsVisible()) {
8030 pSelectedVizRoute = pr;
8031 pFoundVizRoutePoint = prp;
8032 break;
8033 }
8034 }
8035 }
8036
8037 delete proute_array;
8038 }
8039 node = node->GetNext();
8040 }
8041
8042 // Now choose the "best" selections
8043 if (pFoundActiveRoutePoint) {
8044 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8045 m_pSelectedRoute = pSelectedActiveRoute;
8046 } else if (pFoundVizRoutePoint) {
8047 m_pFoundRoutePoint = pFoundVizRoutePoint;
8048 m_pSelectedRoute = pSelectedVizRoute;
8049 } else
8050 // default is first visible point in list
8051 m_pFoundRoutePoint = pFirstVizPoint;
8052
8053 if (m_pSelectedRoute) {
8054 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8055 } else if (m_pFoundRoutePoint) {
8056 seltype |= SELTYPE_MARKPOINT;
8057 }
8058
8059 // Highlight the selected point, to verify the proper right click selection
8060 if (m_pFoundRoutePoint) {
8061 m_pFoundRoutePoint->m_bPtIsSelected = true;
8062 wxRect wp_rect;
8063 RoutePointGui(*m_pFoundRoutePoint)
8064 .CalculateDCRect(m_dc_route, this, &wp_rect);
8065 RefreshRect(wp_rect, true);
8066 }
8067 }
8068
8069 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8070 // routes But call the popup handler with identifier appropriate to the type
8071 if (pFindRouteSeg) // there is at least one select item
8072 {
8073 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8074 SelectableItemList SelList =
8075 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8076
8077 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8078 {
8079 // Choose the first visible route containing segment in the list
8080 wxSelectableItemListNode *node = SelList.GetFirst();
8081 while (node) {
8082 SelectItem *pFindSel = node->GetData();
8083
8084 Route *pr = (Route *)pFindSel->m_pData3;
8085 if (pr->IsVisible()) {
8086 m_pSelectedRoute = pr;
8087 break;
8088 }
8089 node = node->GetNext();
8090 }
8091 }
8092
8093 if (m_pSelectedRoute) {
8094 if (NULL == m_pFoundRoutePoint)
8095 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8096
8097 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8098 if (m_pSelectedRoute->m_bRtIsSelected) {
8099#ifdef ocpnUSE_GL
8100 if (g_bopengl && m_glcc) {
8101 InvalidateGL();
8102 Update();
8103 } else
8104#endif
8105 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8106 }
8107 seltype |= SELTYPE_ROUTESEGMENT;
8108 }
8109 }
8110
8111 if (pFindTrackSeg) {
8112 m_pSelectedTrack = NULL;
8113 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8114 SelectableItemList SelList =
8115 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8116
8117 // Choose the first visible track containing segment in the list
8118 wxSelectableItemListNode *node = SelList.GetFirst();
8119 while (node) {
8120 SelectItem *pFindSel = node->GetData();
8121
8122 Track *pt = (Track *)pFindSel->m_pData3;
8123 if (pt->IsVisible()) {
8124 m_pSelectedTrack = pt;
8125 break;
8126 }
8127 node = node->GetNext();
8128 }
8129 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8130 }
8131
8132 {
8133 if (pFindCurrent) {
8134 // There may be multiple current entries at the same point.
8135 // For example, there often is a current substation (with directions
8136 // specified) co-located with its master. We want to select the
8137 // substation, so that the direction will be properly indicated on the
8138 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8139 // substation)
8140 IDX_entry *pIDX_best_candidate;
8141
8142 SelectItem *pFind = NULL;
8143 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8144 SelectableItemList SelList =
8145 pSelectTC->FindSelectionList(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8146
8147 // Default is first entry
8148 wxSelectableItemListNode *node = SelList.GetFirst();
8149 pFind = node->GetData();
8150 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8151
8152 if (SelList.GetCount() > 1) {
8153 node = node->GetNext();
8154 while (node) {
8155 pFind = node->GetData();
8156 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8157 if (pIDX_candidate->IDX_type == 'c') {
8158 pIDX_best_candidate = pIDX_candidate;
8159 break;
8160 }
8161
8162 node = node->GetNext();
8163 } // while (node)
8164 } else {
8165 wxSelectableItemListNode *node = SelList.GetFirst();
8166 pFind = node->GetData();
8167 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8168 }
8169
8170 m_pIDXCandidate = pIDX_best_candidate;
8171 seltype |= SELTYPE_CURRENTPOINT;
8172 }
8173
8174 else if (pFindTide) {
8175 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8176 seltype |= SELTYPE_TIDEPOINT;
8177 }
8178 }
8179
8180 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8181
8182 return seltype;
8183}
8184
8185void ChartCanvas::CallPopupMenu(int x, int y) {
8186 last_drag.x = x;
8187 last_drag.y = y;
8188 if (m_routeState) { // creating route?
8189 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8190 return;
8191 }
8192
8194
8195 // If tide or current point is selected, then show the TC dialog immediately
8196 // without context menu
8197 if (SELTYPE_CURRENTPOINT == seltype) {
8198 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8199 Refresh(false);
8200 return;
8201 }
8202
8203 if (SELTYPE_TIDEPOINT == seltype) {
8204 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8205 Refresh(false);
8206 return;
8207 }
8208
8209 InvokeCanvasMenu(x, y, seltype);
8210
8211 // Clean up if not deleted in InvokeCanvasMenu
8212 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8213 m_pSelectedRoute->m_bRtIsSelected = false;
8214 }
8215
8216 m_pSelectedRoute = NULL;
8217
8218 if (m_pFoundRoutePoint) {
8219 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8220 m_pFoundRoutePoint->m_bPtIsSelected = false;
8221 }
8222 m_pFoundRoutePoint = NULL;
8223
8224 Refresh(true);
8225 // Refresh(false); // needed for MSW, not GTK Why??
8226}
8227
8228bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8229 // For now just bail out completely if the point clicked is not on the chart
8230 if (std::isnan(m_cursor_lat)) return false;
8231
8232 // Mouse Clicks
8233 bool ret = false; // return true if processed
8234
8235 int x, y, mx, my;
8236 event.GetPosition(&x, &y);
8237 mx = x;
8238 my = y;
8239
8240 // Calculate meaningful SelectRadius
8241 float SelectRadius;
8242 SelectRadius = g_Platform->GetSelectRadiusPix() /
8243 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8244
8246 // We start with Double Click processing. The first left click just starts a
8247 // timer and is remembered, then we actually do something if there is a
8248 // LeftDClick. If there is, the two single clicks are ignored.
8249
8250 if (event.LeftDClick() && (cursor_region == CENTER)) {
8251 m_DoubleClickTimer->Start();
8252 singleClickEventIsValid = false;
8253
8254 double zlat, zlon;
8255 GetCanvasPixPoint(x * g_current_monitor_dip_px_ratio,
8256 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8257
8258 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8259 if (m_bShowAIS) {
8260 SelectItem *pFindAIS;
8261 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8262
8263 if (pFindAIS) {
8264 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8265 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8266 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8267 }
8268 return true;
8269 }
8270 }
8271
8272 SelectableItemList rpSelList =
8273 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8274 wxSelectableItemListNode *node = rpSelList.GetFirst();
8275 bool b_onRPtarget = false;
8276 while (node) {
8277 SelectItem *pFind = node->GetData();
8278 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8279 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8280 b_onRPtarget = true;
8281 break;
8282 }
8283 node = node->GetNext();
8284 }
8285
8286 // Double tap with selected RoutePoint or Mark
8287
8288 if (m_pRoutePointEditTarget) {
8289 if (b_onRPtarget) {
8290 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8291 return true;
8292 } else {
8293 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8294 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8295 if (g_btouch)
8296 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8297 wxRect wp_rect;
8298 RoutePointGui(*m_pRoutePointEditTarget)
8299 .CalculateDCRect(m_dc_route, this, &wp_rect);
8300 m_pRoutePointEditTarget = NULL; // cancel selection
8301 RefreshRect(wp_rect, true);
8302 return true;
8303 }
8304 } else {
8305 node = rpSelList.GetFirst();
8306 if (node) {
8307 SelectItem *pFind = node->GetData();
8308 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8309 if (frp) {
8310 wxArrayPtrVoid *proute_array =
8311 g_pRouteMan->GetRouteArrayContaining(frp);
8312
8313 // Use route array (if any) to determine actual visibility for this
8314 // point
8315 bool brp_viz = false;
8316 if (proute_array) {
8317 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8318 Route *pr = (Route *)proute_array->Item(ir);
8319 if (pr->IsVisible()) {
8320 brp_viz = true;
8321 break;
8322 }
8323 }
8324 delete proute_array;
8325 if (!brp_viz &&
8326 frp->IsShared()) // is not visible as part of route, but still
8327 // exists as a waypoint
8328 brp_viz = frp->IsVisible(); // so treat as isolated point
8329 } else
8330 brp_viz = frp->IsVisible(); // isolated point
8331
8332 if (brp_viz) {
8333 ShowMarkPropertiesDialog(frp);
8334 return true;
8335 }
8336 }
8337 }
8338 }
8339
8340 SelectItem *cursorItem;
8341 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8342
8343 if (cursorItem) {
8344 Route *pr = (Route *)cursorItem->m_pData3;
8345 if (pr->IsVisible()) {
8346 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8347 return true;
8348 }
8349 }
8350
8351 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8352
8353 if (cursorItem) {
8354 Track *pt = (Track *)cursorItem->m_pData3;
8355 if (pt->IsVisible()) {
8356 ShowTrackPropertiesDialog(pt);
8357 return true;
8358 }
8359 }
8360
8361 // Found no object to act on, so show chart info.
8362
8363 ShowObjectQueryWindow(x, y, zlat, zlon);
8364 return true;
8365 }
8366
8368 if (event.LeftDown()) {
8369 // This really should not be needed, but....
8370 // on Windows, when using wxAUIManager, sometimes the focus is lost
8371 // when clicking into another pane, e.g.the AIS target list, and then back
8372 // to this pane. Oddly, some mouse events are not lost, however. Like this
8373 // one....
8374 SetFocus();
8375
8376 last_drag.x = mx;
8377 last_drag.y = my;
8378 leftIsDown = true;
8379
8380 if (!g_btouch) {
8381 if (m_routeState) // creating route?
8382 {
8383 double rlat, rlon;
8384 bool appending = false;
8385 bool inserting = false;
8386 Route *tail = 0;
8387
8388 SetCursor(*pCursorPencil);
8389 rlat = m_cursor_lat;
8390 rlon = m_cursor_lon;
8391
8392 m_bRouteEditing = true;
8393
8394 if (m_routeState == 1) {
8395 m_pMouseRoute = new Route();
8396 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8397 pRouteList->Append(m_pMouseRoute);
8398 r_rband.x = x;
8399 r_rband.y = y;
8400 }
8401
8402 // Check to see if there is a nearby point which may be reused
8403 RoutePoint *pMousePoint = NULL;
8404
8405 // Calculate meaningful SelectRadius
8406 double nearby_radius_meters =
8407 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8408
8409 RoutePoint *pNearbyPoint =
8410 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8411 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8412 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8413 wxArrayPtrVoid *proute_array =
8414 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8415
8416 // Use route array (if any) to determine actual visibility for this
8417 // point
8418 bool brp_viz = false;
8419 if (proute_array) {
8420 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8421 Route *pr = (Route *)proute_array->Item(ir);
8422 if (pr->IsVisible()) {
8423 brp_viz = true;
8424 break;
8425 }
8426 }
8427 delete proute_array;
8428 if (!brp_viz &&
8429 pNearbyPoint->IsShared()) // is not visible as part of route,
8430 // but still exists as a waypoint
8431 brp_viz =
8432 pNearbyPoint->IsVisible(); // so treat as isolated point
8433 } else
8434 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8435
8436 if (brp_viz) {
8437 wxString msg = _("Use nearby waypoint?");
8438 // Don't add a mark without name to the route. Name it if needed
8439 const bool noname(pNearbyPoint->GetName() == "");
8440 if (noname) {
8441 msg =
8442 _("Use nearby nameless waypoint and name it M with"
8443 " a unique number?");
8444 }
8445 // Avoid route finish on focus change for message dialog
8446 m_FinishRouteOnKillFocus = false;
8447 int dlg_return =
8448 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8449 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8450 m_FinishRouteOnKillFocus = true;
8451 if (dlg_return == wxID_YES) {
8452 if (noname) {
8453 if (m_pMouseRoute) {
8454 int last_wp_num = m_pMouseRoute->GetnPoints();
8455 // AP-ECRMB will truncate to 6 characters
8456 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8457 wxString wp_name = wxString::Format(
8458 "M%002i-%s", last_wp_num + 1, guid_short);
8459 pNearbyPoint->SetName(wp_name);
8460 } else
8461 pNearbyPoint->SetName("WPXX");
8462 }
8463 pMousePoint = pNearbyPoint;
8464
8465 // Using existing waypoint, so nothing to delete for undo.
8466 if (m_routeState > 1)
8467 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8468 Undo_HasParent, NULL);
8469
8470 tail =
8471 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8472 bool procede = false;
8473 if (tail) {
8474 procede = true;
8475 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8476 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8477 procede = false;
8478 }
8479
8480 if (procede) {
8481 int dlg_return;
8482 m_FinishRouteOnKillFocus = false;
8483 if (m_routeState ==
8484 1) { // first point in new route, preceeding route to be
8485 // added? Not touch case
8486
8487 wxString dmsg =
8488 _("Insert first part of this route in the new route?");
8489 if (tail->GetIndexOf(pMousePoint) ==
8490 tail->GetnPoints()) // Starting on last point of another
8491 // route?
8492 dmsg = _("Insert this route in the new route?");
8493
8494 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8495 dlg_return = OCPNMessageBox(
8496 this, dmsg, _("OpenCPN Route Create"),
8497 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8498 m_FinishRouteOnKillFocus = true;
8499
8500 if (dlg_return == wxID_YES) {
8501 inserting = true; // part of the other route will be
8502 // preceeding the new route
8503 }
8504 }
8505 } else {
8506 wxString dmsg =
8507 _("Append last part of this route to the new route?");
8508 if (tail->GetIndexOf(pMousePoint) == 1)
8509 dmsg = _(
8510 "Append this route to the new route?"); // Picking the
8511 // first point
8512 // of another
8513 // route?
8514
8515 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8516 dlg_return = OCPNMessageBox(
8517 this, dmsg, _("OpenCPN Route Create"),
8518 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8519 m_FinishRouteOnKillFocus = true;
8520
8521 if (dlg_return == wxID_YES) {
8522 appending = true; // part of the other route will be
8523 // appended to the new route
8524 }
8525 }
8526 }
8527 }
8528
8529 // check all other routes to see if this point appears in any
8530 // other route If it appears in NO other route, then it should e
8531 // considered an isolated mark
8532 if (!FindRouteContainingWaypoint(pMousePoint))
8533 pMousePoint->SetShared(true);
8534 }
8535 }
8536 }
8537
8538 if (NULL == pMousePoint) { // need a new point
8539 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8540 "", wxEmptyString);
8541 pMousePoint->SetNameShown(false);
8542
8543 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8544
8545 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8546
8547 if (m_routeState > 1)
8548 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8549 Undo_IsOrphanded, NULL);
8550 }
8551
8552 if (m_pMouseRoute) {
8553 if (m_routeState == 1) {
8554 // First point in the route.
8555 m_pMouseRoute->AddPoint(pMousePoint);
8556 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8557 } else {
8558 if (m_pMouseRoute->m_NextLegGreatCircle) {
8559 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8560 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8561 &rhumbBearing, &rhumbDist);
8562 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8563 rlat, &gcDist, &gcBearing, NULL);
8564 double gcDistNM = gcDist / 1852.0;
8565
8566 // Empirically found expression to get reasonable route segments.
8567 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8568 pow(rhumbDist - gcDistNM - 1, 0.5);
8569
8570 wxString msg;
8571 msg << _("For this leg the Great Circle route is ")
8572 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8573 << _(" shorter than rhumbline.\n\n")
8574 << _("Would you like include the Great Circle routing points "
8575 "for this leg?");
8576
8577 m_FinishRouteOnKillFocus = false;
8578 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8579 // does not fully capture mouse
8580
8581 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8582 wxYES_NO | wxNO_DEFAULT);
8583
8584 m_disable_edge_pan = false;
8585 m_FinishRouteOnKillFocus = true;
8586
8587 if (answer == wxID_YES) {
8588 RoutePoint *gcPoint;
8589 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8590 wxRealPoint gcCoord;
8591
8592 for (int i = 1; i <= segmentCount; i++) {
8593 double fraction = (double)i * (1.0 / (double)segmentCount);
8594 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8595 gcDist * fraction, gcBearing,
8596 &gcCoord.x, &gcCoord.y, NULL);
8597
8598 if (i < segmentCount) {
8599 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8600 wxEmptyString);
8601 gcPoint->SetNameShown(false);
8602 // pConfig->AddNewWayPoint(gcPoint, -1);
8603 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8604
8605 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8606 gcPoint);
8607 } else {
8608 gcPoint = pMousePoint; // Last point, previously exsisting!
8609 }
8610
8611 m_pMouseRoute->AddPoint(gcPoint);
8612 pSelect->AddSelectableRouteSegment(
8613 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8614 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8615 prevGcPoint = gcPoint;
8616 }
8617
8618 undo->CancelUndoableAction(true);
8619
8620 } else {
8621 m_pMouseRoute->AddPoint(pMousePoint);
8622 pSelect->AddSelectableRouteSegment(
8623 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8624 pMousePoint, m_pMouseRoute);
8625 undo->AfterUndoableAction(m_pMouseRoute);
8626 }
8627 } else {
8628 // Ordinary rhumblinesegment.
8629 m_pMouseRoute->AddPoint(pMousePoint);
8630 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8631 rlon, m_prev_pMousePoint,
8632 pMousePoint, m_pMouseRoute);
8633 undo->AfterUndoableAction(m_pMouseRoute);
8634 }
8635 }
8636 }
8637 m_prev_rlat = rlat;
8638 m_prev_rlon = rlon;
8639 m_prev_pMousePoint = pMousePoint;
8640 if (m_pMouseRoute)
8641 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8642
8643 m_routeState++;
8644
8645 if (appending ||
8646 inserting) { // Appending a route or making a new route
8647 int connect = tail->GetIndexOf(pMousePoint);
8648 if (connect == 1) {
8649 inserting = false; // there is nothing to insert
8650 appending = true; // so append
8651 }
8652 int length = tail->GetnPoints();
8653
8654 int i;
8655 int start, stop;
8656 if (appending) {
8657 start = connect + 1;
8658 stop = length;
8659 } else { // inserting
8660 start = 1;
8661 stop = connect;
8662 m_pMouseRoute->RemovePoint(
8663 m_pMouseRoute
8664 ->GetLastPoint()); // Remove the first and only point
8665 }
8666 for (i = start; i <= stop; i++) {
8667 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8668 if (m_pMouseRoute)
8669 m_pMouseRoute->m_lastMousePointIndex =
8670 m_pMouseRoute->GetnPoints();
8671 m_routeState++;
8672 gFrame->RefreshAllCanvas();
8673 ret = true;
8674 }
8675 m_prev_rlat =
8676 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8677 m_prev_rlon =
8678 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8679 m_pMouseRoute->FinalizeForRendering();
8680 }
8681 gFrame->RefreshAllCanvas();
8682 ret = true;
8683 }
8684
8685 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8686 {
8687 SetCursor(*pCursorPencil);
8688
8689 if (!m_pMeasureRoute) {
8690 m_pMeasureRoute = new Route();
8691 pRouteList->Append(m_pMeasureRoute);
8692 }
8693
8694 if (m_nMeasureState == 1) {
8695 r_rband.x = x;
8696 r_rband.y = y;
8697 }
8698
8699 RoutePoint *pMousePoint = new RoutePoint(m_cursor_lat, m_cursor_lon,
8700 wxString(_T ( "circle" )),
8701 wxEmptyString, wxEmptyString);
8702 pMousePoint->m_bShowName = false;
8703 pMousePoint->SetShowWaypointRangeRings(false);
8704
8705 m_pMeasureRoute->AddPoint(pMousePoint);
8706
8707 m_prev_rlat = m_cursor_lat;
8708 m_prev_rlon = m_cursor_lon;
8709 m_prev_pMousePoint = pMousePoint;
8710 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8711
8712 m_nMeasureState++;
8713 gFrame->RefreshAllCanvas();
8714 ret = true;
8715 }
8716
8717 else {
8718 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8719 }
8720 } // !g_btouch
8721 else { // g_btouch
8722
8723 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8724 // if near screen edge, pan with injection
8725 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8726 // return;
8727 // }
8728 }
8729 }
8730
8731 if (ret) return true;
8732 }
8733
8734 if (event.Dragging()) {
8735 // in touch screen mode ensure the finger/cursor is on the selected point's
8736 // radius to allow dragging
8737 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8738 if (g_btouch) {
8739 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8740 SelectItem *pFind = NULL;
8741 SelectableItemList SelList = pSelect->FindSelectionList(
8742 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8743 wxSelectableItemListNode *node = SelList.GetFirst();
8744 while (node) {
8745 pFind = node->GetData();
8746 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8747 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8748 node = node->GetNext();
8749 }
8750 }
8751
8752 // Check for use of dragHandle
8753 if (m_pRoutePointEditTarget &&
8754 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8755 SelectItem *pFind = NULL;
8756 SelectableItemList SelList = pSelect->FindSelectionList(
8757 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8758 wxSelectableItemListNode *node = SelList.GetFirst();
8759 while (node) {
8760 pFind = node->GetData();
8761 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8762 if (m_pRoutePointEditTarget == frp) {
8763 m_bIsInRadius = true;
8764 break;
8765 }
8766 node = node->GetNext();
8767 }
8768
8769 if (!m_dragoffsetSet) {
8770 RoutePointGui(*m_pRoutePointEditTarget)
8771 .PresetDragOffset(this, mouse_x, mouse_y);
8772 m_dragoffsetSet = true;
8773 }
8774 }
8775 }
8776
8777 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8778 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8779
8780 if (NULL == g_pMarkInfoDialog) {
8781 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8782 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8783 DraggingAllowed = false;
8784
8785 if (m_pRoutePointEditTarget &&
8786 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8787 DraggingAllowed = false;
8788
8789 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8790
8791 if (DraggingAllowed) {
8792 if (!undo->InUndoableAction()) {
8793 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8794 Undo_NeedsCopy, m_pFoundPoint);
8795 }
8796
8797 // Get the update rectangle for the union of the un-edited routes
8798 wxRect pre_rect;
8799
8800 if (!g_bopengl && m_pEditRouteArray) {
8801 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8802 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8803 // Need to validate route pointer
8804 // Route may be gone due to drgging close to ownship with
8805 // "Delete On Arrival" state set, as in the case of
8806 // navigating to an isolated waypoint on a temporary route
8807 if (g_pRouteMan->IsRouteValid(pr)) {
8808 wxRect route_rect;
8809 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8810 pre_rect.Union(route_rect);
8811 }
8812 }
8813 }
8814
8815 double new_cursor_lat = m_cursor_lat;
8816 double new_cursor_lon = m_cursor_lon;
8817
8818 if (CheckEdgePan(x, y, true, 5, 2))
8819 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8820
8821 // update the point itself
8822 if (g_btouch) {
8823 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8824 // new_cursor_lat, new_cursor_lon);
8825 RoutePointGui(*m_pRoutePointEditTarget)
8826 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8827 // update the Drag Handle entry in the pSelect list
8828 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8829 m_pRoutePointEditTarget,
8830 SELTYPE_DRAGHANDLE);
8831 m_pFoundPoint->m_slat =
8832 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8833 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8834 } else {
8835 m_pRoutePointEditTarget->m_lat =
8836 new_cursor_lat; // update the RoutePoint entry
8837 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8838 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8839 m_pFoundPoint->m_slat =
8840 new_cursor_lat; // update the SelectList entry
8841 m_pFoundPoint->m_slon = new_cursor_lon;
8842 }
8843
8844 // Update the MarkProperties Dialog, if currently shown
8845 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8846 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8847 g_pMarkInfoDialog->UpdateProperties(true);
8848 }
8849
8850 if (g_bopengl) {
8851 // InvalidateGL();
8852 Refresh(false);
8853 } else {
8854 // Get the update rectangle for the edited route
8855 wxRect post_rect;
8856
8857 if (m_pEditRouteArray) {
8858 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8859 ir++) {
8860 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8861 if (g_pRouteMan->IsRouteValid(pr)) {
8862 wxRect route_rect;
8863 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8864 post_rect.Union(route_rect);
8865 }
8866 }
8867 }
8868
8869 // Invalidate the union region
8870 pre_rect.Union(post_rect);
8871 RefreshRect(pre_rect, false);
8872 }
8873 gFrame->RefreshCanvasOther(this);
8874 m_bRoutePoinDragging = true;
8875 }
8876 ret = true;
8877 } // if Route Editing
8878
8879 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8880 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8881
8882 if (NULL == g_pMarkInfoDialog) {
8883 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8884 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8885 DraggingAllowed = false;
8886
8887 if (m_pRoutePointEditTarget &&
8888 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8889 DraggingAllowed = false;
8890
8891 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8892
8893 if (DraggingAllowed) {
8894 if (!undo->InUndoableAction()) {
8895 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8896 Undo_NeedsCopy, m_pFoundPoint);
8897 }
8898
8899 // The mark may be an anchorwatch
8900 double lpp1 = 0.;
8901 double lpp2 = 0.;
8902 double lppmax;
8903
8904 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8905 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8906 }
8907 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8908 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8909 }
8910 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8911
8912 // Get the update rectangle for the un-edited mark
8913 wxRect pre_rect;
8914 if (!g_bopengl) {
8915 RoutePointGui(*m_pRoutePointEditTarget)
8916 .CalculateDCRect(m_dc_route, this, &pre_rect);
8917 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8918 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8919 (int)(lppmax - (pre_rect.height / 2)));
8920 }
8921
8922 // update the point itself
8923 if (g_btouch) {
8924 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8925 // m_cursor_lat, m_cursor_lon);
8926 RoutePointGui(*m_pRoutePointEditTarget)
8927 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8928 // update the Drag Handle entry in the pSelect list
8929 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
8930 m_pRoutePointEditTarget,
8931 SELTYPE_DRAGHANDLE);
8932 m_pFoundPoint->m_slat =
8933 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8934 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8935 } else {
8936 m_pRoutePointEditTarget->m_lat =
8937 m_cursor_lat; // update the RoutePoint entry
8938 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
8939 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8940 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
8941 m_pFoundPoint->m_slon = m_cursor_lon;
8942 }
8943
8944 // Update the MarkProperties Dialog, if currently shown
8945 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8946 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8947 g_pMarkInfoDialog->UpdateProperties(true);
8948 }
8949
8950 // Invalidate the union region
8951 if (g_bopengl) {
8952 if (!g_btouch) InvalidateGL();
8953 Refresh(false);
8954 } else {
8955 // Get the update rectangle for the edited mark
8956 wxRect post_rect;
8957 RoutePointGui(*m_pRoutePointEditTarget)
8958 .CalculateDCRect(m_dc_route, this, &post_rect);
8959 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
8960 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
8961 (int)(lppmax - (post_rect.height / 2)));
8962
8963 // Invalidate the union region
8964 pre_rect.Union(post_rect);
8965 RefreshRect(pre_rect, false);
8966 }
8967 gFrame->RefreshCanvasOther(this);
8968 m_bRoutePoinDragging = true;
8969 }
8970 ret = true;
8971 }
8972
8973 if (ret) return true;
8974 } // dragging
8975
8976 if (event.LeftUp()) {
8977 bool b_startedit_route = false;
8978 m_dragoffsetSet = false;
8979
8980 if (g_btouch) {
8981 m_bChartDragging = false;
8982 m_bIsInRadius = false;
8983
8984 if (m_routeState) // creating route?
8985 {
8986 if (m_bedge_pan) {
8987 m_bedge_pan = false;
8988 return false;
8989 }
8990
8991 double rlat, rlon;
8992 bool appending = false;
8993 bool inserting = false;
8994 Route *tail = 0;
8995
8996 rlat = m_cursor_lat;
8997 rlon = m_cursor_lon;
8998
8999 if (m_pRoutePointEditTarget) {
9000 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9001 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9002 if (!g_bopengl) {
9003 wxRect wp_rect;
9004 RoutePointGui(*m_pRoutePointEditTarget)
9005 .CalculateDCRect(m_dc_route, this, &wp_rect);
9006 RefreshRect(wp_rect, true);
9007 }
9008 m_pRoutePointEditTarget = NULL;
9009 }
9010 m_bRouteEditing = true;
9011
9012 if (m_routeState == 1) {
9013 m_pMouseRoute = new Route();
9014 m_pMouseRoute->SetHiLite(50);
9015 pRouteList->Append(m_pMouseRoute);
9016 r_rband.x = x;
9017 r_rband.y = y;
9018 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9019 }
9020
9021 // Check to see if there is a nearby point which may be reused
9022 RoutePoint *pMousePoint = NULL;
9023
9024 // Calculate meaningful SelectRadius
9025 double nearby_radius_meters =
9026 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9027
9028 RoutePoint *pNearbyPoint =
9029 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9030 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9031 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9032 int dlg_return;
9033#ifndef __WXOSX__
9034 m_FinishRouteOnKillFocus =
9035 false; // Avoid route finish on focus change for message dialog
9036 dlg_return = OCPNMessageBox(
9037 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9038 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9039 m_FinishRouteOnKillFocus = true;
9040#else
9041 dlg_return = wxID_YES;
9042#endif
9043 if (dlg_return == wxID_YES) {
9044 pMousePoint = pNearbyPoint;
9045
9046 // Using existing waypoint, so nothing to delete for undo.
9047 if (m_routeState > 1)
9048 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9049 Undo_HasParent, NULL);
9050 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9051
9052 bool procede = false;
9053 if (tail) {
9054 procede = true;
9055 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9056 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9057 procede = false;
9058 }
9059
9060 if (procede) {
9061 int dlg_return;
9062 m_FinishRouteOnKillFocus = false;
9063 if (m_routeState == 1) { // first point in new route, preceeding
9064 // route to be added? touch case
9065
9066 wxString dmsg =
9067 _("Insert first part of this route in the new route?");
9068 if (tail->GetIndexOf(pMousePoint) ==
9069 tail->GetnPoints()) // Starting on last point of another
9070 // route?
9071 dmsg = _("Insert this route in the new route?");
9072
9073 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9074 dlg_return =
9075 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9076 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9077 m_FinishRouteOnKillFocus = true;
9078
9079 if (dlg_return == wxID_YES) {
9080 inserting = true; // part of the other route will be
9081 // preceeding the new route
9082 }
9083 }
9084 } else {
9085 wxString dmsg =
9086 _("Append last part of this route to the new route?");
9087 if (tail->GetIndexOf(pMousePoint) == 1)
9088 dmsg = _(
9089 "Append this route to the new route?"); // Picking the
9090 // first point of
9091 // another route?
9092
9093 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9094 dlg_return =
9095 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9096 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9097 m_FinishRouteOnKillFocus = true;
9098
9099 if (dlg_return == wxID_YES) {
9100 appending = true; // part of the other route will be
9101 // appended to the new route
9102 }
9103 }
9104 }
9105 }
9106
9107 // check all other routes to see if this point appears in any other
9108 // route If it appears in NO other route, then it should e
9109 // considered an isolated mark
9110 if (!FindRouteContainingWaypoint(pMousePoint))
9111 pMousePoint->SetShared(true);
9112 }
9113 }
9114
9115 if (NULL == pMousePoint) { // need a new point
9116 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9117 "", wxEmptyString);
9118 pMousePoint->SetNameShown(false);
9119
9120 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9121
9122 if (m_routeState > 1)
9123 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9124 Undo_IsOrphanded, NULL);
9125 }
9126
9127 if (m_routeState == 1) {
9128 // First point in the route.
9129 m_pMouseRoute->AddPoint(pMousePoint);
9130 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9131
9132 } else {
9133 if (m_pMouseRoute->m_NextLegGreatCircle) {
9134 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9135 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9136 &rhumbBearing, &rhumbDist);
9137 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9138 &gcDist, &gcBearing, NULL);
9139 double gcDistNM = gcDist / 1852.0;
9140
9141 // Empirically found expression to get reasonable route segments.
9142 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9143 pow(rhumbDist - gcDistNM - 1, 0.5);
9144
9145 wxString msg;
9146 msg << _("For this leg the Great Circle route is ")
9147 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9148 << _(" shorter than rhumbline.\n\n")
9149 << _("Would you like include the Great Circle routing points "
9150 "for this leg?");
9151
9152#ifndef __WXOSX__
9153 m_FinishRouteOnKillFocus = false;
9154 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9155 wxYES_NO | wxNO_DEFAULT);
9156 m_FinishRouteOnKillFocus = true;
9157#else
9158 int answer = wxID_NO;
9159#endif
9160
9161 if (answer == wxID_YES) {
9162 RoutePoint *gcPoint;
9163 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9164 wxRealPoint gcCoord;
9165
9166 for (int i = 1; i <= segmentCount; i++) {
9167 double fraction = (double)i * (1.0 / (double)segmentCount);
9168 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9169 gcDist * fraction, gcBearing,
9170 &gcCoord.x, &gcCoord.y, NULL);
9171
9172 if (i < segmentCount) {
9173 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9174 wxEmptyString);
9175 gcPoint->SetNameShown(false);
9176 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9177 gcPoint);
9178 } else {
9179 gcPoint = pMousePoint; // Last point, previously exsisting!
9180 }
9181
9182 m_pMouseRoute->AddPoint(gcPoint);
9183 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9184
9185 pSelect->AddSelectableRouteSegment(
9186 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9187 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9188 prevGcPoint = gcPoint;
9189 }
9190
9191 undo->CancelUndoableAction(true);
9192
9193 } else {
9194 m_pMouseRoute->AddPoint(pMousePoint);
9195 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9196 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9197 rlon, m_prev_pMousePoint,
9198 pMousePoint, m_pMouseRoute);
9199 undo->AfterUndoableAction(m_pMouseRoute);
9200 }
9201 } else {
9202 // Ordinary rhumblinesegment.
9203 m_pMouseRoute->AddPoint(pMousePoint);
9204 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9205
9206 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9207 rlon, m_prev_pMousePoint,
9208 pMousePoint, m_pMouseRoute);
9209 undo->AfterUndoableAction(m_pMouseRoute);
9210 }
9211 }
9212
9213 m_prev_rlat = rlat;
9214 m_prev_rlon = rlon;
9215 m_prev_pMousePoint = pMousePoint;
9216 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9217
9218 m_routeState++;
9219
9220 if (appending ||
9221 inserting) { // Appending a route or making a new route
9222 int connect = tail->GetIndexOf(pMousePoint);
9223 if (connect == 1) {
9224 inserting = false; // there is nothing to insert
9225 appending = true; // so append
9226 }
9227 int length = tail->GetnPoints();
9228
9229 int i;
9230 int start, stop;
9231 if (appending) {
9232 start = connect + 1;
9233 stop = length;
9234 } else { // inserting
9235 start = 1;
9236 stop = connect;
9237 m_pMouseRoute->RemovePoint(
9238 m_pMouseRoute
9239 ->GetLastPoint()); // Remove the first and only point
9240 }
9241 for (i = start; i <= stop; i++) {
9242 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9243 if (m_pMouseRoute)
9244 m_pMouseRoute->m_lastMousePointIndex =
9245 m_pMouseRoute->GetnPoints();
9246 m_routeState++;
9247 gFrame->RefreshAllCanvas();
9248 ret = true;
9249 }
9250 m_prev_rlat =
9251 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9252 m_prev_rlon =
9253 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9254 m_pMouseRoute->FinalizeForRendering();
9255 }
9256
9257 Refresh(true);
9258 ret = true;
9259 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9260 {
9261 if (m_bedge_pan) {
9262 m_bedge_pan = false;
9263 return false;
9264 }
9265
9266 if (m_nMeasureState == 1) {
9267 m_pMeasureRoute = new Route();
9268 pRouteList->Append(m_pMeasureRoute);
9269 r_rband.x = x;
9270 r_rband.y = y;
9271 }
9272
9273 if (m_pMeasureRoute) {
9274 RoutePoint *pMousePoint = new RoutePoint(
9275 m_cursor_lat, m_cursor_lon, wxString(_T ( "circle" )),
9276 wxEmptyString, wxEmptyString);
9277 pMousePoint->m_bShowName = false;
9278
9279 m_pMeasureRoute->AddPoint(pMousePoint);
9280
9281 m_prev_rlat = m_cursor_lat;
9282 m_prev_rlon = m_cursor_lon;
9283 m_prev_pMousePoint = pMousePoint;
9284 m_pMeasureRoute->m_lastMousePointIndex =
9285 m_pMeasureRoute->GetnPoints();
9286
9287 m_nMeasureState++;
9288 } else {
9289 CancelMeasureRoute();
9290 }
9291
9292 Refresh(true);
9293 ret = true;
9294 } else {
9295 bool bSelectAllowed = true;
9296 if (NULL == g_pMarkInfoDialog) {
9297 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9298 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9299 bSelectAllowed = false;
9300
9301 /*if this left up happens at the end of a route point dragging and if
9302 the cursor/thumb is on the draghandle icon, not on the point iself a new
9303 selection will select nothing and the drag will never be ended, so the
9304 legs around this point never selectable. At this step we don't need a
9305 new selection, just keep the previoulsly selected and dragged point */
9306 if (m_bRoutePoinDragging) bSelectAllowed = false;
9307
9308 if (bSelectAllowed) {
9309 bool b_was_editing_mark = m_bMarkEditing;
9310 bool b_was_editing_route = m_bRouteEditing;
9311 FindRoutePointsAtCursor(SelectRadius,
9312 true); // Possibly selecting a point in a
9313 // route for later dragging
9314
9315 /*route and a mark points in layer can't be dragged so should't be
9316 * selected and no draghandle icon*/
9317 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9318 m_pRoutePointEditTarget = NULL;
9319
9320 if (!b_was_editing_route) {
9321 if (m_pEditRouteArray) {
9322 b_startedit_route = true;
9323
9324 // Hide the track and route rollover during route point edit, not
9325 // needed, and may be confusing
9326 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9327 m_pTrackRolloverWin->IsActive(false);
9328 }
9329 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9330 m_pRouteRolloverWin->IsActive(false);
9331 }
9332
9333 wxRect pre_rect;
9334 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9335 ir++) {
9336 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9337 // Need to validate route pointer
9338 // Route may be gone due to drgging close to ownship with
9339 // "Delete On Arrival" state set, as in the case of
9340 // navigating to an isolated waypoint on a temporary route
9341 if (g_pRouteMan->IsRouteValid(pr)) {
9342 // pr->SetHiLite(50);
9343 wxRect route_rect;
9344 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9345 pre_rect.Union(route_rect);
9346 }
9347 }
9348 RefreshRect(pre_rect, true);
9349 }
9350 } else {
9351 b_startedit_route = false;
9352 }
9353
9354 // Mark editing
9355 if (m_pRoutePointEditTarget) {
9356 if (b_was_editing_mark ||
9357 b_was_editing_route) { // kill previous hilight
9358 if (m_lastRoutePointEditTarget) {
9359 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9360 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9361 RoutePointGui(*m_lastRoutePointEditTarget)
9362 .EnableDragHandle(false);
9363 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9364 SELTYPE_DRAGHANDLE);
9365 }
9366 }
9367
9368 if (m_pRoutePointEditTarget) {
9369 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9370 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9371 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9372 wxPoint2DDouble dragHandlePoint =
9373 RoutePointGui(*m_pRoutePointEditTarget)
9374 .GetDragHandlePoint(this);
9375 pSelect->AddSelectablePoint(
9376 dragHandlePoint.m_y, dragHandlePoint.m_x,
9377 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9378 }
9379 } else { // Deselect everything
9380 if (m_lastRoutePointEditTarget) {
9381 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9382 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9383 RoutePointGui(*m_lastRoutePointEditTarget)
9384 .EnableDragHandle(false);
9385 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9386 SELTYPE_DRAGHANDLE);
9387
9388 // Clear any routes being edited, probably orphans
9389 wxArrayPtrVoid *lastEditRouteArray =
9390 g_pRouteMan->GetRouteArrayContaining(
9391 m_lastRoutePointEditTarget);
9392 if (lastEditRouteArray) {
9393 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9394 ir++) {
9395 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9396 if (g_pRouteMan->IsRouteValid(pr)) {
9397 pr->m_bIsBeingEdited = false;
9398 }
9399 }
9400 delete lastEditRouteArray;
9401 }
9402 }
9403 }
9404
9405 // Do the refresh
9406
9407 if (g_bopengl) {
9408 InvalidateGL();
9409 Refresh(false);
9410 } else {
9411 if (m_lastRoutePointEditTarget) {
9412 wxRect wp_rect;
9413 RoutePointGui(*m_lastRoutePointEditTarget)
9414 .CalculateDCRect(m_dc_route, this, &wp_rect);
9415 RefreshRect(wp_rect, true);
9416 }
9417
9418 if (m_pRoutePointEditTarget) {
9419 wxRect wp_rect;
9420 RoutePointGui(*m_pRoutePointEditTarget)
9421 .CalculateDCRect(m_dc_route, this, &wp_rect);
9422 RefreshRect(wp_rect, true);
9423 }
9424 }
9425 }
9426 } // bSelectAllowed
9427
9428 // Check to see if there is a route or AIS target under the cursor
9429 // If so, start the rollover timer which creates the popup
9430 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9431 bool b_start_rollover = false;
9432 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9433 SelectItem *pFind = pSelectAIS->FindSelection(
9434 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9435 if (pFind) b_start_rollover = true;
9436 }
9437
9438 if (!b_start_rollover && !b_startedit_route) {
9439 SelectableItemList SelList = pSelect->FindSelectionList(
9440 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9441 wxSelectableItemListNode *node = SelList.GetFirst();
9442 while (node) {
9443 SelectItem *pFindSel = node->GetData();
9444
9445 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9446
9447 if (pr && pr->IsVisible()) {
9448 b_start_rollover = true;
9449 break;
9450 }
9451 node = node->GetNext();
9452 } // while
9453 }
9454
9455 if (!b_start_rollover && !b_startedit_route) {
9456 SelectableItemList SelList = pSelect->FindSelectionList(
9457 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9458 wxSelectableItemListNode *node = SelList.GetFirst();
9459 while (node) {
9460 SelectItem *pFindSel = node->GetData();
9461
9462 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9463
9464 if (tr && tr->IsVisible()) {
9465 b_start_rollover = true;
9466 break;
9467 }
9468 node = node->GetNext();
9469 } // while
9470 }
9471
9472 if (b_start_rollover)
9473 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9474 wxTIMER_ONE_SHOT);
9475 Route *tail = 0;
9476 Route *current = 0;
9477 bool appending = false;
9478 bool inserting = false;
9479 int connect = 0;
9480 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9481 // drag
9482 if (m_pRoutePointEditTarget) {
9483 // Check to see if there is a nearby point which may replace the
9484 // dragged one
9485 RoutePoint *pMousePoint = NULL;
9486
9487 int index_last;
9488 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9489 double nearby_radius_meters =
9490 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9491 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9492 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9493 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9494 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9495 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9496 bool duplicate =
9497 false; // ensure we won't create duplicate point in routes
9498 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9499 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9500 ir++) {
9501 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9502 if (pr && pr->pRoutePointList) {
9503 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9504 wxNOT_FOUND) {
9505 duplicate = true;
9506 break;
9507 }
9508 }
9509 }
9510 }
9511
9512 // Special case:
9513 // Allow "re-use" of a route's waypoints iff it is a simple
9514 // isolated route. This allows, for instance, creation of a closed
9515 // polygon route
9516 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9517
9518 if (!duplicate) {
9519 int dlg_return;
9520 dlg_return =
9521 OCPNMessageBox(this,
9522 _("Replace this RoutePoint by the nearby "
9523 "Waypoint?"),
9524 _("OpenCPN RoutePoint change"),
9525 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9526 if (dlg_return == wxID_YES) {
9527 /*double confirmation if the dragged point has been manually
9528 * created which can be important and could be deleted
9529 * unintentionally*/
9530
9531 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9532 pNearbyPoint);
9533 current =
9534 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9535
9536 if (tail && current && (tail != current)) {
9537 int dlg_return1;
9538 connect = tail->GetIndexOf(pNearbyPoint);
9539 int index_current_route =
9540 current->GetIndexOf(m_pRoutePointEditTarget);
9541 index_last = current->GetIndexOf(current->GetLastPoint());
9542 dlg_return1 = wxID_NO;
9543 if (index_last ==
9544 index_current_route) { // we are dragging the last
9545 // point of the route
9546 if (connect != tail->GetnPoints()) { // anything to do?
9547
9548 wxString dmsg(
9549 _("Last part of route to be appended to dragged "
9550 "route?"));
9551 if (connect == 1)
9552 dmsg =
9553 _("Full route to be appended to dragged route?");
9554
9555 dlg_return1 = OCPNMessageBox(
9556 this, dmsg, _("OpenCPN Route Create"),
9557 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9558 if (dlg_return1 == wxID_YES) {
9559 appending = true;
9560 }
9561 }
9562 } else if (index_current_route ==
9563 1) { // dragging the first point of the route
9564 if (connect != 1) { // anything to do?
9565
9566 wxString dmsg(
9567 _("First part of route to be inserted into dragged "
9568 "route?"));
9569 if (connect == tail->GetnPoints())
9570 dmsg = _(
9571 "Full route to be inserted into dragged route?");
9572
9573 dlg_return1 = OCPNMessageBox(
9574 this, dmsg, _("OpenCPN Route Create"),
9575 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9576 if (dlg_return1 == wxID_YES) {
9577 inserting = true;
9578 }
9579 }
9580 }
9581 }
9582
9583 if (m_pRoutePointEditTarget->IsShared()) {
9584 // dlg_return = wxID_NO;
9585 dlg_return = OCPNMessageBox(
9586 this,
9587 _("Do you really want to delete and replace this "
9588 "WayPoint") +
9589 "\n" + _("which has been created manually?"),
9590 ("OpenCPN RoutePoint warning"),
9591 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9592 }
9593 }
9594 if (dlg_return == wxID_YES) {
9595 pMousePoint = pNearbyPoint;
9596 if (pMousePoint->m_bIsolatedMark) {
9597 pMousePoint->SetShared(true);
9598 }
9599 pMousePoint->m_bIsolatedMark =
9600 false; // definitely no longer isolated
9601 pMousePoint->m_bIsInRoute = true;
9602 }
9603 }
9604 }
9605 }
9606 if (!pMousePoint)
9607 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9608
9609 if (m_pEditRouteArray) {
9610 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9611 ir++) {
9612 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9613 if (g_pRouteMan->IsRouteValid(pr)) {
9614 if (pMousePoint) { // remove the dragged point and insert the
9615 // nearby
9616 int nRP =
9617 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9618
9619 pSelect->DeleteAllSelectableRoutePoints(pr);
9620 pSelect->DeleteAllSelectableRouteSegments(pr);
9621
9622 pr->pRoutePointList->Insert(nRP, pMousePoint);
9623 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9624
9625 pSelect->AddAllSelectableRouteSegments(pr);
9626 pSelect->AddAllSelectableRoutePoints(pr);
9627 }
9628 pr->FinalizeForRendering();
9629 pr->UpdateSegmentDistances();
9630 if (m_bRoutePoinDragging) {
9631 // pConfig->UpdateRoute(pr);
9632 NavObj_dB::GetInstance().UpdateRoute(pr);
9633 }
9634 }
9635 }
9636 }
9637
9638 // Update the RouteProperties Dialog, if currently shown
9639 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9640 if (m_pEditRouteArray) {
9641 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9642 ir++) {
9643 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9644 if (g_pRouteMan->IsRouteValid(pr)) {
9645 if (pRoutePropDialog->GetRoute() == pr) {
9646 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9647 }
9648 /* cannot edit track points anyway
9649 else if ( ( NULL !=
9650 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9651 pTrackPropDialog->m_pTrack == pr ) {
9652 pTrackPropDialog->SetTrackAndUpdate(
9653 pr );
9654 }
9655 */
9656 }
9657 }
9658 }
9659 }
9660 if (pMousePoint) { // clear all about the dragged point
9661 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9662 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9663 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9664 // Hide mark properties dialog if open on the replaced point
9665 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9666 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9667 g_pMarkInfoDialog->Hide();
9668
9669 delete m_pRoutePointEditTarget;
9670 m_lastRoutePointEditTarget = NULL;
9671 m_pRoutePointEditTarget = NULL;
9672 undo->AfterUndoableAction(pMousePoint);
9673 undo->InvalidateUndo();
9674 }
9675 }
9676 }
9677
9678 else if (m_bMarkEditing) { // End of way point drag
9679 if (m_pRoutePointEditTarget)
9680 if (m_bRoutePoinDragging) {
9681 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9682 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9683 }
9684 }
9685
9686 if (m_pRoutePointEditTarget)
9687 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9688
9689 if (!m_pRoutePointEditTarget) {
9690 delete m_pEditRouteArray;
9691 m_pEditRouteArray = NULL;
9692 m_bRouteEditing = false;
9693 }
9694 m_bRoutePoinDragging = false;
9695
9696 if (appending) { // Appending to the route of which the last point is
9697 // dragged onto another route
9698
9699 // copy tail from connect until length to end of current after dragging
9700
9701 int length = tail->GetnPoints();
9702 for (int i = connect + 1; i <= length; i++) {
9703 current->AddPointAndSegment(tail->GetPoint(i), false);
9704 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9705 m_routeState++;
9706 gFrame->RefreshAllCanvas();
9707 ret = true;
9708 }
9709 current->FinalizeForRendering();
9710 current->m_bIsBeingEdited = false;
9711 FinishRoute();
9712 g_pRouteMan->DeleteRoute(tail);
9713 }
9714 if (inserting) {
9715 pSelect->DeleteAllSelectableRoutePoints(current);
9716 pSelect->DeleteAllSelectableRouteSegments(current);
9717 for (int i = 1; i < connect; i++) { // numbering in the tail route
9718 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9719 }
9720 pSelect->AddAllSelectableRouteSegments(current);
9721 pSelect->AddAllSelectableRoutePoints(current);
9722 current->FinalizeForRendering();
9723 current->m_bIsBeingEdited = false;
9724 g_pRouteMan->DeleteRoute(tail);
9725 }
9726
9727 // Update the RouteProperties Dialog, if currently shown
9728 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9729 if (m_pEditRouteArray) {
9730 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9731 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9732 if (g_pRouteMan->IsRouteValid(pr)) {
9733 if (pRoutePropDialog->GetRoute() == pr) {
9734 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9735 }
9736 }
9737 }
9738 }
9739 }
9740
9741 } // g_btouch
9742
9743 else { // !g_btouch
9744 if (m_bRouteEditing) { // End of RoutePoint drag
9745 Route *tail = 0;
9746 Route *current = 0;
9747 bool appending = false;
9748 bool inserting = false;
9749 int connect = 0;
9750 int index_last;
9751 if (m_pRoutePointEditTarget) {
9752 m_pRoutePointEditTarget->m_bBlink = false;
9753 // Check to see if there is a nearby point which may replace the
9754 // dragged one
9755 RoutePoint *pMousePoint = NULL;
9756 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9757 double nearby_radius_meters =
9758 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9759 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9760 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9761 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9762 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9763 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9764 bool duplicate = false; // don't create duplicate point in routes
9765 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9766 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9767 ir++) {
9768 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9769 if (pr && pr->pRoutePointList) {
9770 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9771 wxNOT_FOUND) {
9772 duplicate = true;
9773 break;
9774 }
9775 }
9776 }
9777 }
9778
9779 // Special case:
9780 // Allow "re-use" of a route's waypoints iff it is a simple
9781 // isolated route. This allows, for instance, creation of a closed
9782 // polygon route
9783 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9784
9785 if (!duplicate) {
9786 int dlg_return;
9787 dlg_return =
9788 OCPNMessageBox(this,
9789 _("Replace this RoutePoint by the nearby "
9790 "Waypoint?"),
9791 _("OpenCPN RoutePoint change"),
9792 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9793 if (dlg_return == wxID_YES) {
9794 /*double confirmation if the dragged point has been manually
9795 * created which can be important and could be deleted
9796 * unintentionally*/
9797 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9798 pNearbyPoint);
9799 current =
9800 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9801
9802 if (tail && current && (tail != current)) {
9803 int dlg_return1;
9804 connect = tail->GetIndexOf(pNearbyPoint);
9805 int index_current_route =
9806 current->GetIndexOf(m_pRoutePointEditTarget);
9807 index_last = current->GetIndexOf(current->GetLastPoint());
9808 dlg_return1 = wxID_NO;
9809 if (index_last ==
9810 index_current_route) { // we are dragging the last
9811 // point of the route
9812 if (connect != tail->GetnPoints()) { // anything to do?
9813
9814 wxString dmsg(
9815 _("Last part of route to be appended to dragged "
9816 "route?"));
9817 if (connect == 1)
9818 dmsg =
9819 _("Full route to be appended to dragged route?");
9820
9821 dlg_return1 = OCPNMessageBox(
9822 this, dmsg, _("OpenCPN Route Create"),
9823 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9824 if (dlg_return1 == wxID_YES) {
9825 appending = true;
9826 }
9827 }
9828 } else if (index_current_route ==
9829 1) { // dragging the first point of the route
9830 if (connect != 1) { // anything to do?
9831
9832 wxString dmsg(
9833 _("First part of route to be inserted into dragged "
9834 "route?"));
9835 if (connect == tail->GetnPoints())
9836 dmsg = _(
9837 "Full route to be inserted into dragged route?");
9838
9839 dlg_return1 = OCPNMessageBox(
9840 this, dmsg, _("OpenCPN Route Create"),
9841 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9842 if (dlg_return1 == wxID_YES) {
9843 inserting = true;
9844 }
9845 }
9846 }
9847 }
9848
9849 if (m_pRoutePointEditTarget->IsShared()) {
9850 dlg_return = wxID_NO;
9851 dlg_return = OCPNMessageBox(
9852 this,
9853 _("Do you really want to delete and replace this "
9854 "WayPoint") +
9855 "\n" + _("which has been created manually?"),
9856 ("OpenCPN RoutePoint warning"),
9857 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9858 }
9859 }
9860 if (dlg_return == wxID_YES) {
9861 pMousePoint = pNearbyPoint;
9862 if (pMousePoint->m_bIsolatedMark) {
9863 pMousePoint->SetShared(true);
9864 }
9865 pMousePoint->m_bIsolatedMark =
9866 false; // definitely no longer isolated
9867 pMousePoint->m_bIsInRoute = true;
9868 }
9869 }
9870 }
9871 }
9872 if (!pMousePoint)
9873 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9874
9875 if (m_pEditRouteArray) {
9876 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9877 ir++) {
9878 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9879 if (g_pRouteMan->IsRouteValid(pr)) {
9880 if (pMousePoint) { // replace dragged point by nearby one
9881 int nRP =
9882 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9883
9884 pSelect->DeleteAllSelectableRoutePoints(pr);
9885 pSelect->DeleteAllSelectableRouteSegments(pr);
9886
9887 pr->pRoutePointList->Insert(nRP, pMousePoint);
9888 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9889
9890 pSelect->AddAllSelectableRouteSegments(pr);
9891 pSelect->AddAllSelectableRoutePoints(pr);
9892 }
9893 pr->FinalizeForRendering();
9894 pr->UpdateSegmentDistances();
9895 pr->m_bIsBeingEdited = false;
9896
9897 if (m_bRoutePoinDragging) {
9898 // Special case optimization.
9899 // Dragging a single point of a route
9900 // without any point additions or re-ordering
9901 if (!pMousePoint)
9902 NavObj_dB::GetInstance().UpdateRoutePoint(
9903 m_pRoutePointEditTarget);
9904 else
9905 NavObj_dB::GetInstance().UpdateRoute(pr);
9906 }
9907 pr->SetHiLite(0);
9908 }
9909 }
9910 Refresh(false);
9911 }
9912
9913 if (appending) {
9914 // copy tail from connect until length to end of current after
9915 // dragging
9916
9917 int length = tail->GetnPoints();
9918 for (int i = connect + 1; i <= length; i++) {
9919 current->AddPointAndSegment(tail->GetPoint(i), false);
9920 if (current)
9921 current->m_lastMousePointIndex = current->GetnPoints();
9922 m_routeState++;
9923 gFrame->RefreshAllCanvas();
9924 ret = true;
9925 }
9926 current->FinalizeForRendering();
9927 current->m_bIsBeingEdited = false;
9928 FinishRoute();
9929 g_pRouteMan->DeleteRoute(tail);
9930 }
9931 if (inserting) {
9932 pSelect->DeleteAllSelectableRoutePoints(current);
9933 pSelect->DeleteAllSelectableRouteSegments(current);
9934 for (int i = 1; i < connect; i++) { // numbering in the tail route
9935 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9936 }
9937 pSelect->AddAllSelectableRouteSegments(current);
9938 pSelect->AddAllSelectableRoutePoints(current);
9939 current->FinalizeForRendering();
9940 current->m_bIsBeingEdited = false;
9941 g_pRouteMan->DeleteRoute(tail);
9942 }
9943
9944 // Update the RouteProperties Dialog, if currently shown
9945 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9946 if (m_pEditRouteArray) {
9947 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9948 ir++) {
9949 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9950 if (g_pRouteMan->IsRouteValid(pr)) {
9951 if (pRoutePropDialog->GetRoute() == pr) {
9952 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9953 }
9954 }
9955 }
9956 }
9957 }
9958
9959 if (pMousePoint) {
9960 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9961 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9962 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9963 // Hide mark properties dialog if open on the replaced point
9964 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9965 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9966 g_pMarkInfoDialog->Hide();
9967
9968 delete m_pRoutePointEditTarget;
9969 m_lastRoutePointEditTarget = NULL;
9970 undo->AfterUndoableAction(pMousePoint);
9971 undo->InvalidateUndo();
9972 } else {
9973 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9974 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9975
9976 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9977 }
9978
9979 delete m_pEditRouteArray;
9980 m_pEditRouteArray = NULL;
9981 }
9982
9983 InvalidateGL();
9984 m_bRouteEditing = false;
9985 m_pRoutePointEditTarget = NULL;
9986
9987 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9988 ret = true;
9989 }
9990
9991 else if (m_bMarkEditing) { // end of Waypoint drag
9992 if (m_pRoutePointEditTarget) {
9993 if (m_bRoutePoinDragging) {
9994 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9995 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9996 }
9997 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9998 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9999 if (!g_bopengl) {
10000 wxRect wp_rect;
10001 RoutePointGui(*m_pRoutePointEditTarget)
10002 .CalculateDCRect(m_dc_route, this, &wp_rect);
10003 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10004 RefreshRect(wp_rect, true);
10005 }
10006 }
10007 m_pRoutePointEditTarget = NULL;
10008 m_bMarkEditing = false;
10009 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10010 ret = true;
10011 }
10012
10013 else if (leftIsDown) { // left click for chart center
10014 leftIsDown = false;
10015 ret = false;
10016
10017 if (!g_btouch) {
10018 if (!m_bChartDragging && !m_bMeasure_Active) {
10019 } else {
10020 m_bChartDragging = false;
10021 }
10022 }
10023 }
10024 m_bRoutePoinDragging = false;
10025 } // !btouch
10026
10027 if (ret) return true;
10028 } // left up
10029
10030 if (event.RightDown()) {
10031 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10032 last_drag.x = mx;
10033 last_drag.y = my;
10034
10035 if (g_btouch) {
10036 // if( m_pRoutePointEditTarget )
10037 // return false;
10038 }
10039
10040 ret = true;
10041 m_FinishRouteOnKillFocus = false;
10042 CallPopupMenu(mx, my);
10043 m_FinishRouteOnKillFocus = true;
10044 } // Right down
10045
10046 return ret;
10047}
10048
10049bool panleftIsDown;
10050
10051bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10052 // Skip all mouse processing if shift is held.
10053 // This allows plugins to implement shift+drag behaviors.
10054 if (event.ShiftDown()) {
10055 return false;
10056 }
10057 int x, y;
10058 event.GetPosition(&x, &y);
10059
10060 x *= m_displayScale;
10061 y *= m_displayScale;
10062
10063 // Check for wheel rotation
10064 // ideally, should be just longer than the time between
10065 // processing accumulated mouse events from the event queue
10066 // as would happen during screen redraws.
10067 int wheel_dir = event.GetWheelRotation();
10068
10069 if (wheel_dir) {
10070 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10071 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10072
10073 double factor = g_mouse_zoom_sensitivity;
10074 if (wheel_dir < 0) factor = 1 / factor;
10075
10076 if (g_bsmoothpanzoom) {
10077 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10078 if (wheel_dir == m_last_wheel_dir) {
10079 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10080 // m_zoom_target /= factor;
10081 } else
10082 StopMovement();
10083 } else {
10084 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10085 m_wheelstopwatch.Start(0);
10086 // m_zoom_target = VPoint.chart_scale / factor;
10087 }
10088 }
10089
10090 m_last_wheel_dir = wheel_dir;
10091
10092 ZoomCanvas(factor, true, false);
10093 }
10094
10095 if (event.LeftDown()) {
10096 // Skip the first left click if it will cause a canvas focus shift
10097 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10098 // printf("focus shift\n");
10099 return false;
10100 }
10101
10102 last_drag.x = x, last_drag.y = y;
10103 panleftIsDown = true;
10104 }
10105
10106 if (event.LeftUp()) {
10107 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10108 // seen here.
10109 panleftIsDown = false;
10110
10111 if (!g_btouch) {
10112 if (!m_bChartDragging && !m_bMeasure_Active) {
10113 switch (cursor_region) {
10114 case MID_RIGHT: {
10115 PanCanvas(100, 0);
10116 break;
10117 }
10118
10119 case MID_LEFT: {
10120 PanCanvas(-100, 0);
10121 break;
10122 }
10123
10124 case MID_TOP: {
10125 PanCanvas(0, 100);
10126 break;
10127 }
10128
10129 case MID_BOT: {
10130 PanCanvas(0, -100);
10131 break;
10132 }
10133
10134 case CENTER: {
10135 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10136 break;
10137 }
10138 }
10139 } else {
10140 m_bChartDragging = false;
10141 }
10142 }
10143 }
10144 }
10145
10146 if (event.Dragging() && event.LeftIsDown()) {
10147 /*
10148 * fixed dragging.
10149 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10150 * before the drag event. Hence, as there is no mouse down event, last_drag
10151 * is not reset before the drag. And that results in one single drag
10152 * session, meaning you cannot drag the map a few miles north, lift your
10153 * finger, and the go even further north. Instead, the map resets itself
10154 * always to the very first drag start (since there is not reset of
10155 * last_drag).
10156 *
10157 * Besides, should not left down and dragging be enough of a situation to
10158 * start a drag procedure?
10159 *
10160 * Anyways, guarded it to be active in touch situations only.
10161 */
10162
10163 if (g_btouch) {
10164 struct timespec now;
10165 clock_gettime(CLOCK_MONOTONIC, &now);
10166 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10167
10168 if (false == m_bChartDragging) {
10169 // Reset drag calculation members
10170 last_drag.x = x, last_drag.y = y;
10171 m_bChartDragging = true;
10172 m_chart_drag_total_time = 0;
10173 m_chart_drag_total_x = 0;
10174 m_chart_drag_total_y = 0;
10175 m_inertia_last_drag_x = x;
10176 m_inertia_last_drag_y = y;
10177 m_drag_vec_x.clear();
10178 m_drag_vec_y.clear();
10179 m_drag_vec_t.clear();
10180 m_last_drag_time = tnow;
10181 }
10182
10183 // Calculate and store drag dynamics.
10184 uint64_t delta_t = tnow - m_last_drag_time;
10185 double delta_tf = delta_t / 1e9;
10186
10187 m_chart_drag_total_time += delta_tf;
10188 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10189 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10190
10191 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10192 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10193 m_drag_vec_t.push_back(delta_tf);
10194
10195 m_inertia_last_drag_x = x;
10196 m_inertia_last_drag_y = y;
10197 m_last_drag_time = tnow;
10198
10199 if ((last_drag.x != x) || (last_drag.y != y)) {
10200 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10201 // dragging on route create.
10202 // github #2994
10203 m_bChartDragging = true;
10204 StartTimedMovement();
10205 m_pan_drag.x += last_drag.x - x;
10206 m_pan_drag.y += last_drag.y - y;
10207 last_drag.x = x, last_drag.y = y;
10208 }
10209 }
10210 } else {
10211 if ((last_drag.x != x) || (last_drag.y != y)) {
10212 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10213 // dragging on route create.
10214 // github #2994
10215 m_bChartDragging = true;
10216 StartTimedMovement();
10217 m_pan_drag.x += last_drag.x - x;
10218 m_pan_drag.y += last_drag.y - y;
10219 last_drag.x = x, last_drag.y = y;
10220 }
10221 }
10222 }
10223
10224 // Handle some special cases
10225 if (g_btouch) {
10226 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10227 // deactivate next LeftUp to ovoid creating an unexpected point
10228 m_DoubleClickTimer->Start();
10229 singleClickEventIsValid = false;
10230 }
10231 }
10232 }
10233
10234 return true;
10235}
10236
10237void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10238 if (MouseEventOverlayWindows(event)) return;
10239
10240 if (MouseEventSetup(event)) return; // handled, no further action required
10241
10242 if (!MouseEventProcessObjects(event)) MouseEventProcessCanvas(event);
10243}
10244
10245void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10246 // Switch to the appropriate cursor on mouse movement
10247
10248 wxCursor *ptarget_cursor = pCursorArrow;
10249 if (!pPlugIn_Cursor) {
10250 ptarget_cursor = pCursorArrow;
10251 if ((!m_routeState) &&
10252 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10253 if (cursor_region == MID_RIGHT) {
10254 ptarget_cursor = pCursorRight;
10255 } else if (cursor_region == MID_LEFT) {
10256 ptarget_cursor = pCursorLeft;
10257 } else if (cursor_region == MID_TOP) {
10258 ptarget_cursor = pCursorDown;
10259 } else if (cursor_region == MID_BOT) {
10260 ptarget_cursor = pCursorUp;
10261 } else {
10262 ptarget_cursor = pCursorArrow;
10263 }
10264 } else if (m_bMeasure_Active ||
10265 m_routeState) // If Measure tool use Pencil Cursor
10266 ptarget_cursor = pCursorPencil;
10267 } else {
10268 ptarget_cursor = pPlugIn_Cursor;
10269 }
10270
10271 SetCursor(*ptarget_cursor);
10272}
10273
10274void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10275 SetCursor(*pCursorArrow);
10276}
10277
10278void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10279 ChartPlugInWrapper *target_plugin_chart = NULL;
10280 s57chart *Chs57 = NULL;
10281 wxFileName file;
10282 wxArrayString files;
10283
10284 ChartBase *target_chart = GetChartAtCursor();
10285 if (target_chart) {
10286 file.Assign(target_chart->GetFullPath());
10287 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10288 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10289 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10290 else
10291 Chs57 = dynamic_cast<s57chart *>(target_chart);
10292 } else { // target_chart = null, might be mbtiles
10293 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10294 unsigned int im = stackIndexArray.size();
10295 int scale = 2147483647; // max 32b integer
10296 if (VPoint.b_quilt && im > 0) {
10297 for (unsigned int is = 0; is < im; is++) {
10298 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10299 CHART_TYPE_MBTILES) {
10300 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10301 double lat, lon;
10302 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10303 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10304 .GetBBox()
10305 .Contains(lat, lon)) {
10306 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10307 scale) {
10308 scale =
10309 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10310 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10311 }
10312 }
10313 }
10314 }
10315 }
10316 }
10317
10318 std::vector<Ais8_001_22 *> area_notices;
10319
10320 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10321 float vp_scale = GetVPScale();
10322
10323 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10324 auto target_data = target.second;
10325 if (!target_data->area_notices.empty()) {
10326 for (auto &ani : target_data->area_notices) {
10327 Ais8_001_22 &area_notice = ani.second;
10328
10329 BoundingBox bbox;
10330
10331 for (Ais8_001_22_SubAreaList::iterator sa =
10332 area_notice.sub_areas.begin();
10333 sa != area_notice.sub_areas.end(); ++sa) {
10334 switch (sa->shape) {
10335 case AIS8_001_22_SHAPE_CIRCLE: {
10336 wxPoint target_point;
10337 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10338 bbox.Expand(target_point);
10339 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10340 break;
10341 }
10342 case AIS8_001_22_SHAPE_RECT: {
10343 wxPoint target_point;
10344 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10345 bbox.Expand(target_point);
10346 if (sa->e_dim_m > sa->n_dim_m)
10347 bbox.EnLarge(sa->e_dim_m * vp_scale);
10348 else
10349 bbox.EnLarge(sa->n_dim_m * vp_scale);
10350 break;
10351 }
10352 case AIS8_001_22_SHAPE_POLYGON:
10353 case AIS8_001_22_SHAPE_POLYLINE: {
10354 for (int i = 0; i < 4; ++i) {
10355 double lat = sa->latitude;
10356 double lon = sa->longitude;
10357 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10358 &lat, &lon);
10359 wxPoint target_point;
10360 GetCanvasPointPix(lat, lon, &target_point);
10361 bbox.Expand(target_point);
10362 }
10363 break;
10364 }
10365 case AIS8_001_22_SHAPE_SECTOR: {
10366 double lat1 = sa->latitude;
10367 double lon1 = sa->longitude;
10368 double lat, lon;
10369 wxPoint target_point;
10370 GetCanvasPointPix(lat1, lon1, &target_point);
10371 bbox.Expand(target_point);
10372 for (int i = 0; i < 18; ++i) {
10373 ll_gc_ll(
10374 lat1, lon1,
10375 sa->left_bound_deg +
10376 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10377 sa->radius_m / 1852.0, &lat, &lon);
10378 GetCanvasPointPix(lat, lon, &target_point);
10379 bbox.Expand(target_point);
10380 }
10381 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10382 &lat, &lon);
10383 GetCanvasPointPix(lat, lon, &target_point);
10384 bbox.Expand(target_point);
10385 break;
10386 }
10387 }
10388 }
10389
10390 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10391 area_notices.push_back(&area_notice);
10392 }
10393 }
10394 }
10395 }
10396 }
10397
10398 if (target_chart || !area_notices.empty() || file.HasName()) {
10399 // Go get the array of all objects at the cursor lat/lon
10400 int sel_rad_pix = 5;
10401 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10402
10403 // Make sure we always get the lights from an object, even if we are
10404 // currently not displaying lights on the chart.
10405
10406 SetCursor(wxCURSOR_WAIT);
10407 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10408 if (!lightsVis) SetShowENCLights(true);
10409 ;
10410
10411 ListOfObjRazRules *rule_list = NULL;
10412 ListOfPI_S57Obj *pi_rule_list = NULL;
10413 if (Chs57)
10414 rule_list =
10415 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10416 else if (target_plugin_chart)
10417 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10418 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10419
10420 ListOfObjRazRules *overlay_rule_list = NULL;
10421 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10422 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10423
10424 if (CHs57_Overlay) {
10425 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10426 zlat, zlon, SelectRadius, &GetVP());
10427 }
10428
10429 if (!lightsVis) SetShowENCLights(false);
10430
10431 wxString objText;
10432 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10433 wxString face = dFont->GetFaceName();
10434
10435 if (NULL == g_pObjectQueryDialog) {
10436 g_pObjectQueryDialog =
10437 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10438 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10439 }
10440
10441 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10442 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10443
10444#ifdef __WXOSX__
10445 // Auto Adjustment for dark mode
10446 fg = g_pObjectQueryDialog->GetForegroundColour();
10447#endif
10448
10449 objText.Printf(
10450 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10451 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10452
10453#ifdef __WXOSX__
10454 int points = dFont->GetPointSize();
10455#else
10456 int points = dFont->GetPointSize() + 1;
10457#endif
10458
10459 int sizes[7];
10460 for (int i = -2; i < 5; i++) {
10461 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10462 }
10463 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10464
10465 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10466
10467 if (overlay_rule_list && CHs57_Overlay) {
10468 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10469 objText << "<hr noshade>";
10470 }
10471
10472 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10473 an != area_notices.end(); ++an) {
10474 objText << _T( "<b>AIS Area Notice:</b> " );
10475 objText << ais8_001_22_notice_names[(*an)->notice_type];
10476 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10477 (*an)->sub_areas.begin();
10478 sa != (*an)->sub_areas.end(); ++sa)
10479 if (!sa->text.empty()) objText << sa->text;
10480 objText << _T( "<br>expires: " ) << (*an)->expiry_time.Format();
10481 objText << _T( "<hr noshade>" );
10482 }
10483
10484 if (Chs57)
10485 objText << Chs57->CreateObjDescriptions(rule_list);
10486 else if (target_plugin_chart)
10487 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10488 pi_rule_list);
10489
10490 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10491
10492 // Add the additional info files
10493 wxString AddFiles, filenameOK;
10494 int filecount = 0;
10495 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10496 // plugin
10497
10498 AddFiles = wxString::Format(
10499 "<hr noshade><br><b>Additional info files attached to: </b> "
10500 "<font "
10501 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10502 "cellpadding=3>",
10503 file.GetFullName());
10504 file.Normalize();
10505 file.Assign(file.GetPath(), "");
10506 wxDir dir(file.GetFullPath());
10507 wxString filename;
10508 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10509 while (cont) {
10510 file.Assign(dir.GetNameWithSep().append(filename));
10511 wxString FormatString =
10512 "<td valign=top><font size=-2><a "
10513 "href=\"%s\">%s</a></font></td>";
10514 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10515 filenameOK = file.GetFullPath(); // remember last valid name
10516 // we are making a 3 columns table. New row only every third file
10517 if (3 * ((int)filecount / 3) == filecount)
10518 FormatString.Prepend("<tr>"); // new row
10519 else
10520 FormatString.Prepend(
10521 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10522 // spacer column
10523
10524 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10525 file.GetFullName());
10526 filecount++;
10527 }
10528 cont = dir.GetNext(&filename);
10529 }
10530 objText << AddFiles << "</table>";
10531 }
10532 objText << "</font>";
10533 objText << "</body></html>";
10534
10535 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10536 g_pObjectQueryDialog->SetHTMLPage(objText);
10537 g_pObjectQueryDialog->Show();
10538 }
10539 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10540 // generate an event to avoid double code
10541 wxHtmlLinkInfo hli(filenameOK);
10542 wxHtmlLinkEvent hle(1, hli);
10543 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10544 }
10545
10546 if (rule_list) rule_list->Clear();
10547 delete rule_list;
10548
10549 if (overlay_rule_list) overlay_rule_list->Clear();
10550 delete overlay_rule_list;
10551
10552 if (pi_rule_list) pi_rule_list->Clear();
10553 delete pi_rule_list;
10554
10555 SetCursor(wxCURSOR_ARROW);
10556 }
10557}
10558
10559void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10560 bool bNew = false;
10561 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10562 // Dialog
10563 g_pMarkInfoDialog = new MarkInfoDlg(this);
10564 bNew = true;
10565 }
10566
10567 if (1 /*g_bresponsive*/) {
10568 wxSize canvas_size = GetSize();
10569
10570 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10571 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10572
10573 g_pMarkInfoDialog->Layout();
10574
10575 wxPoint canvas_pos = GetPosition();
10576 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10577
10578 bool newFit = false;
10579 if (canvas_size.x < fitted_size.x) {
10580 fitted_size.x = canvas_size.x - 40;
10581 if (canvas_size.y < fitted_size.y)
10582 fitted_size.y -= 40; // scrollbar added
10583 }
10584 if (canvas_size.y < fitted_size.y) {
10585 fitted_size.y = canvas_size.y - 40;
10586 if (canvas_size.x < fitted_size.x)
10587 fitted_size.x -= 40; // scrollbar added
10588 }
10589
10590 if (newFit) {
10591 g_pMarkInfoDialog->SetSize(fitted_size);
10592 g_pMarkInfoDialog->Centre();
10593 }
10594 }
10595
10596 markPoint->m_bRPIsBeingEdited = false;
10597
10598 wxString title_base = _("Mark Properties");
10599 if (markPoint->m_bIsInRoute) {
10600 title_base = _("Waypoint Properties");
10601 }
10602 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10603 g_pMarkInfoDialog->UpdateProperties();
10604 if (markPoint->m_bIsInLayer) {
10605 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10606 GetLayerName(markPoint->m_LayerID)));
10607 g_pMarkInfoDialog->SetDialogTitle(caption);
10608 } else
10609 g_pMarkInfoDialog->SetDialogTitle(title_base);
10610
10611 g_pMarkInfoDialog->Show();
10612 g_pMarkInfoDialog->Raise();
10613 g_pMarkInfoDialog->InitialFocus();
10614 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10615}
10616
10617void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10618 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10619 pRoutePropDialog->SetRouteAndUpdate(selected);
10620 // pNew->UpdateProperties();
10621 pRoutePropDialog->Show();
10622 pRoutePropDialog->Raise();
10623 return;
10624 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10625 this); // There is one global instance of the RouteProp Dialog
10626
10627 if (g_bresponsive) {
10628 wxSize canvas_size = GetSize();
10629 wxPoint canvas_pos = GetPosition();
10630 wxSize fitted_size = pRoutePropDialog->GetSize();
10631 ;
10632
10633 if (canvas_size.x < fitted_size.x) {
10634 fitted_size.x = canvas_size.x;
10635 if (canvas_size.y < fitted_size.y)
10636 fitted_size.y -= 20; // scrollbar added
10637 }
10638 if (canvas_size.y < fitted_size.y) {
10639 fitted_size.y = canvas_size.y;
10640 if (canvas_size.x < fitted_size.x)
10641 fitted_size.x -= 20; // scrollbar added
10642 }
10643
10644 pRoutePropDialog->SetSize(fitted_size);
10645 pRoutePropDialog->Centre();
10646
10647 // int xp = (canvas_size.x - fitted_size.x)/2;
10648 // int yp = (canvas_size.y - fitted_size.y)/2;
10649
10650 wxPoint xxp = ClientToScreen(canvas_pos);
10651 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10652 }
10653
10654 pRoutePropDialog->SetRouteAndUpdate(selected);
10655
10656 pRoutePropDialog->Show();
10657
10658 Refresh(false);
10659}
10660
10661void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10662 pTrackPropDialog = TrackPropDlg::getInstance(
10663 this); // There is one global instance of the RouteProp Dialog
10664
10665 pTrackPropDialog->SetTrackAndUpdate(selected);
10666 pTrackPropDialog->UpdateProperties();
10667
10668 pTrackPropDialog->Show();
10669
10670 Refresh(false);
10671}
10672
10673void pupHandler_PasteWaypoint() {
10674 Kml kml;
10675
10676 int pasteBuffer = kml.ParsePasteBuffer();
10677 RoutePoint *pasted = kml.GetParsedRoutePoint();
10678 if (!pasted) return;
10679
10680 double nearby_radius_meters =
10681 g_Platform->GetSelectRadiusPix() /
10682 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10683
10684 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10685 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10686
10687 int answer = wxID_NO;
10688 if (nearPoint && !nearPoint->m_bIsInLayer) {
10689 wxString msg;
10690 msg << _(
10691 "There is an existing waypoint at the same location as the one you are "
10692 "pasting. Would you like to merge the pasted data with it?\n\n");
10693 msg << _("Answering 'No' will create a new waypoint at the same location.");
10694 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10695 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10696 }
10697
10698 if (answer == wxID_YES) {
10699 nearPoint->SetName(pasted->GetName());
10700 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10701 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10702 pRouteManagerDialog->UpdateWptListCtrl();
10703 }
10704
10705 if (answer == wxID_NO) {
10706 RoutePoint *newPoint = new RoutePoint(pasted);
10707 newPoint->m_bIsolatedMark = true;
10708 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10709 newPoint);
10710 // pConfig->AddNewWayPoint(newPoint, -1);
10711 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10712
10713 pWayPointMan->AddRoutePoint(newPoint);
10714 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10715 pRouteManagerDialog->UpdateWptListCtrl();
10716 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10717 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10718 }
10719
10720 gFrame->InvalidateAllGL();
10721 gFrame->RefreshAllCanvas(false);
10722}
10723
10724void pupHandler_PasteRoute() {
10725 Kml kml;
10726
10727 int pasteBuffer = kml.ParsePasteBuffer();
10728 Route *pasted = kml.GetParsedRoute();
10729 if (!pasted) return;
10730
10731 double nearby_radius_meters =
10732 g_Platform->GetSelectRadiusPix() /
10733 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10734
10735 RoutePoint *curPoint;
10736 RoutePoint *nearPoint;
10737 RoutePoint *prevPoint = NULL;
10738
10739 bool mergepoints = false;
10740 bool createNewRoute = true;
10741 int existingWaypointCounter = 0;
10742
10743 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10744 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10745 nearPoint = pWayPointMan->GetNearbyWaypoint(
10746 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10747 if (nearPoint) {
10748 mergepoints = true;
10749 existingWaypointCounter++;
10750 // Small hack here to avoid both extending RoutePoint and repeating all
10751 // the GetNearbyWaypoint calculations. Use existin data field in
10752 // RoutePoint as temporary storage.
10753 curPoint->m_bPtIsSelected = true;
10754 }
10755 }
10756
10757 int answer = wxID_NO;
10758 if (mergepoints) {
10759 wxString msg;
10760 msg << _(
10761 "There are existing waypoints at the same location as some of the ones "
10762 "you are pasting. Would you like to just merge the pasted data into "
10763 "them?\n\n");
10764 msg << _("Answering 'No' will create all new waypoints for this route.");
10765 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10766 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10767
10768 if (answer == wxID_CANCEL) {
10769 return;
10770 }
10771 }
10772
10773 // If all waypoints exist since before, and a route with the same name, we
10774 // don't create a new route.
10775 if (mergepoints && answer == wxID_YES &&
10776 existingWaypointCounter == pasted->GetnPoints()) {
10777 wxRouteListNode *route_node = pRouteList->GetFirst();
10778 while (route_node) {
10779 Route *proute = route_node->GetData();
10780
10781 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10782 createNewRoute = false;
10783 break;
10784 }
10785 route_node = route_node->GetNext();
10786 }
10787 }
10788
10789 Route *newRoute = 0;
10790 RoutePoint *newPoint = 0;
10791
10792 if (createNewRoute) {
10793 newRoute = new Route();
10794 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10795 }
10796
10797 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10798 curPoint = pasted->GetPoint(i);
10799 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10800 curPoint->m_bPtIsSelected = false;
10801 newPoint = pWayPointMan->GetNearbyWaypoint(
10802 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10803 newPoint->SetName(curPoint->GetName());
10804 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10805
10806 if (createNewRoute) newRoute->AddPoint(newPoint);
10807 } else {
10808 curPoint->m_bPtIsSelected = false;
10809
10810 newPoint = new RoutePoint(curPoint);
10811 newPoint->m_bIsolatedMark = false;
10812 newPoint->SetIconName("circle");
10813 newPoint->m_bIsVisible = true;
10814 newPoint->m_bShowName = false;
10815 newPoint->SetShared(false);
10816
10817 newRoute->AddPoint(newPoint);
10818 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10819 newPoint);
10820 // pConfig->AddNewWayPoint(newPoint, -1);
10821 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10822 pWayPointMan->AddRoutePoint(newPoint);
10823 }
10824 if (i > 1 && createNewRoute)
10825 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10826 curPoint->m_lat, curPoint->m_lon,
10827 prevPoint, newPoint, newRoute);
10828 prevPoint = newPoint;
10829 }
10830
10831 if (createNewRoute) {
10832 pRouteList->Append(newRoute);
10833 // pConfig->AddNewRoute(newRoute); // use auto next num
10834 NavObj_dB::GetInstance().InsertRoute(newRoute);
10835
10836 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10837 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10838 }
10839
10840 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10841 pRouteManagerDialog->UpdateRouteListCtrl();
10842 pRouteManagerDialog->UpdateWptListCtrl();
10843 }
10844 gFrame->InvalidateAllGL();
10845 gFrame->RefreshAllCanvas(false);
10846 }
10847 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10848 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10849}
10850
10851void pupHandler_PasteTrack() {
10852 Kml kml;
10853
10854 int pasteBuffer = kml.ParsePasteBuffer();
10855 Track *pasted = kml.GetParsedTrack();
10856 if (!pasted) return;
10857
10858 TrackPoint *curPoint;
10859
10860 Track *newTrack = new Track();
10861 TrackPoint *newPoint;
10862 TrackPoint *prevPoint = NULL;
10863
10864 newTrack->SetName(pasted->GetName());
10865
10866 for (int i = 0; i < pasted->GetnPoints(); i++) {
10867 curPoint = pasted->GetPoint(i);
10868
10869 newPoint = new TrackPoint(curPoint);
10870
10871 wxDateTime now = wxDateTime::Now();
10872 newPoint->SetCreateTime(curPoint->GetCreateTime());
10873
10874 newTrack->AddPoint(newPoint);
10875
10876 if (prevPoint)
10877 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10878 newPoint->m_lat, newPoint->m_lon,
10879 prevPoint, newPoint, newTrack);
10880
10881 prevPoint = newPoint;
10882 }
10883
10884 g_TrackList.push_back(newTrack);
10885 // pConfig->AddNewTrack(newTrack);
10886 NavObj_dB::GetInstance().InsertTrack(newTrack);
10887
10888 gFrame->InvalidateAllGL();
10889 gFrame->RefreshAllCanvas(false);
10890}
10891
10892bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
10893 wxJSONValue v;
10894 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
10895 v["CursorPosition_x"] = x;
10896 v["CursorPosition_y"] = y;
10897 // Send a limited set of selection types depending on what is
10898 // found under the mouse point.
10899 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
10900 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
10901 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
10902
10903 wxJSONWriter w;
10904 wxString out;
10905 w.Write(v, out);
10906 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
10907
10908 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
10909
10910#if 0
10911#define SELTYPE_UNKNOWN 0x0001
10912#define SELTYPE_ROUTEPOINT 0x0002
10913#define SELTYPE_ROUTESEGMENT 0x0004
10914#define SELTYPE_TIDEPOINT 0x0008
10915#define SELTYPE_CURRENTPOINT 0x0010
10916#define SELTYPE_ROUTECREATE 0x0020
10917#define SELTYPE_AISTARGET 0x0040
10918#define SELTYPE_MARKPOINT 0x0080
10919#define SELTYPE_TRACKSEGMENT 0x0100
10920#define SELTYPE_DRAGHANDLE 0x0200
10921#endif
10922
10923 if (g_bhide_context_menus) return true;
10924 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
10925 m_pFoundRoutePoint, m_FoundAIS_MMSI,
10926 m_pIDXCandidate, m_nmea_log);
10927
10928 Connect(
10929 wxEVT_COMMAND_MENU_SELECTED,
10930 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10931
10932 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
10933
10934 Disconnect(
10935 wxEVT_COMMAND_MENU_SELECTED,
10936 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10937
10938 delete m_canvasMenu;
10939 m_canvasMenu = NULL;
10940
10941#ifdef __WXQT__
10942 // gFrame->SurfaceToolbar();
10943 // g_MainToolbar->Raise();
10944#endif
10945
10946 return true;
10947}
10948
10949void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
10950 // Pass menu events from the canvas to the menu handler
10951 // This is necessarily in ChartCanvas since that is the menu's parent.
10952 if (m_canvasMenu) {
10953 m_canvasMenu->PopupMenuHandler(event);
10954 }
10955 return;
10956}
10957
10958void ChartCanvas::StartRoute(void) {
10959 // Do not allow more than one canvas to create a route at one time.
10960 if (g_brouteCreating) return;
10961
10962 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
10963
10964 g_brouteCreating = true;
10965 m_routeState = 1;
10966 m_bDrawingRoute = false;
10967 SetCursor(*pCursorPencil);
10968 // SetCanvasToolbarItemState(ID_ROUTE, true);
10969 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
10970
10971 HideGlobalToolbar();
10972
10973#ifdef __ANDROID__
10974 androidSetRouteAnnunciator(true);
10975#endif
10976}
10977
10978wxString ChartCanvas::FinishRoute(void) {
10979 m_routeState = 0;
10980 m_prev_pMousePoint = NULL;
10981 m_bDrawingRoute = false;
10982 wxString rv = "";
10983 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
10984
10985 // SetCanvasToolbarItemState(ID_ROUTE, false);
10986 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
10987#ifdef __ANDROID__
10988 androidSetRouteAnnunciator(false);
10989#endif
10990
10991 SetCursor(*pCursorArrow);
10992
10993 if (m_pMouseRoute) {
10994 if (m_bAppendingRoute) {
10995 // pConfig->UpdateRoute(m_pMouseRoute);
10996 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
10997 } else {
10998 if (m_pMouseRoute->GetnPoints() > 1) {
10999 // pConfig->AddNewRoute(m_pMouseRoute);
11000 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11001 } else {
11002 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11003 m_pMouseRoute = NULL;
11004 }
11005 }
11006 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11007
11008 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11009 (pRoutePropDialog->IsShown())) {
11010 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11011 }
11012
11013 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11014 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11015 pRouteManagerDialog->UpdateRouteListCtrl();
11016 }
11017 }
11018 m_bAppendingRoute = false;
11019 m_pMouseRoute = NULL;
11020
11021 m_pSelectedRoute = NULL;
11022
11023 undo->InvalidateUndo();
11024 gFrame->RefreshAllCanvas(true);
11025
11026 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11027
11028 ShowGlobalToolbar();
11029
11030 g_brouteCreating = false;
11031
11032 return rv;
11033}
11034
11035void ChartCanvas::HideGlobalToolbar() {
11036 if (m_canvasIndex == 0) {
11037 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
11038 }
11039}
11040
11041void ChartCanvas::ShowGlobalToolbar() {
11042 if (m_canvasIndex == 0) {
11043 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
11044 }
11045}
11046
11047void ChartCanvas::ShowAISTargetList(void) {
11048 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11049 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11050 }
11051
11052 g_pAISTargetList->UpdateAISTargetList();
11053}
11054
11055void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11056 if (!m_bShowOutlines) return;
11057
11058 if (!ChartData) return;
11059
11060 int nEntry = ChartData->GetChartTableEntries();
11061
11062 for (int i = 0; i < nEntry; i++) {
11063 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11064
11065 // Check to see if the candidate chart is in the currently active group
11066 bool b_group_draw = false;
11067 if (m_groupIndex > 0) {
11068 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11069 int index = pt->GetGroupArray()[ig];
11070 if (m_groupIndex == index) {
11071 b_group_draw = true;
11072 break;
11073 }
11074 }
11075 } else
11076 b_group_draw = true;
11077
11078 if (b_group_draw) RenderChartOutline(dc, i, vp);
11079 }
11080
11081 // On CM93 Composite Charts, draw the outlines of the next smaller
11082 // scale cell
11083 cm93compchart *pcm93 = NULL;
11084 if (VPoint.b_quilt) {
11085 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11086 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11087 pcm93 = (cm93compchart *)pch;
11088 break;
11089 }
11090 } else if (m_singleChart &&
11091 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11092 pcm93 = (cm93compchart *)m_singleChart;
11093
11094 if (pcm93) {
11095 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11096 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11097
11098 if (zoom_factor > 8.0) {
11099 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11100 dc.SetPen(mPen);
11101 } else {
11102 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11103 dc.SetPen(mPen);
11104 }
11105
11106 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11107 }
11108}
11109
11110void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11111#ifdef ocpnUSE_GL
11112 if (g_bopengl && m_glcc) {
11113 /* opengl version specially optimized */
11114 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11115 return;
11116 }
11117#endif
11118
11119 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11120 if (!ChartData->IsChartAvailable(dbIndex)) return;
11121 }
11122
11123 float plylat, plylon;
11124 float plylat1, plylon1;
11125
11126 int pixx, pixy, pixx1, pixy1;
11127
11128 LLBBox box;
11129 ChartData->GetDBBoundingBox(dbIndex, box);
11130
11131 // Don't draw an outline in the case where the chart covers the entire world
11132 // */
11133 if (box.GetLonRange() == 360) return;
11134
11135 double lon_bias = 0;
11136 // chart is outside of viewport lat/lon bounding box
11137 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11138
11139 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11140
11141 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11142 dc.SetPen(wxPen(GetGlobalColor(_T ( "YELO1" )), 1, wxPENSTYLE_SOLID));
11143
11144 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11145 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFG" )), 1, wxPENSTYLE_SOLID));
11146
11147 else
11148 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFR" )), 1, wxPENSTYLE_SOLID));
11149
11150 // Are there any aux ply entries?
11151 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11152 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11153 {
11154 wxPoint r, r1;
11155
11156 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11157 plylon += lon_bias;
11158
11159 GetCanvasPointPix(plylat, plylon, &r);
11160 pixx = r.x;
11161 pixy = r.y;
11162
11163 for (int i = 0; i < nPly - 1; i++) {
11164 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11165 plylon1 += lon_bias;
11166
11167 GetCanvasPointPix(plylat1, plylon1, &r1);
11168 pixx1 = r1.x;
11169 pixy1 = r1.y;
11170
11171 int pixxs1 = pixx1;
11172 int pixys1 = pixy1;
11173
11174 bool b_skip = false;
11175
11176 if (vp.chart_scale > 5e7) {
11177 // calculate projected distance between these two points in meters
11178 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11179 pow((double)(pixy1 - pixy), 2)) /
11180 vp.view_scale_ppm;
11181
11182 if (dist > 0.0) {
11183 // calculate GC distance between these two points in meters
11184 double distgc =
11185 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11186
11187 // If the distances are nonsense, it means that the scale is very
11188 // small and the segment wrapped the world So skip it....
11189 // TODO improve this to draw two segments
11190 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11191 b_skip = true;
11192 } else
11193 b_skip = true;
11194 }
11195
11196 ClipResult res = cohen_sutherland_line_clip_i(
11197 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11198 if (res != Invisible && !b_skip)
11199 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11200
11201 plylat = plylat1;
11202 plylon = plylon1;
11203 pixx = pixxs1;
11204 pixy = pixys1;
11205 }
11206
11207 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11208 plylon1 += lon_bias;
11209
11210 GetCanvasPointPix(plylat1, plylon1, &r1);
11211 pixx1 = r1.x;
11212 pixy1 = r1.y;
11213
11214 ClipResult res = cohen_sutherland_line_clip_i(
11215 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11216 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11217 }
11218
11219 else // Use Aux PlyPoints
11220 {
11221 wxPoint r, r1;
11222
11223 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11224 for (int j = 0; j < nAuxPlyEntries; j++) {
11225 int nAuxPly =
11226 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11227 GetCanvasPointPix(plylat, plylon, &r);
11228 pixx = r.x;
11229 pixy = r.y;
11230
11231 for (int i = 0; i < nAuxPly - 1; i++) {
11232 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11233
11234 GetCanvasPointPix(plylat1, plylon1, &r1);
11235 pixx1 = r1.x;
11236 pixy1 = r1.y;
11237
11238 int pixxs1 = pixx1;
11239 int pixys1 = pixy1;
11240
11241 bool b_skip = false;
11242
11243 if (vp.chart_scale > 5e7) {
11244 // calculate projected distance between these two points in meters
11245 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11246 ((pixy1 - pixy) * (pixy1 - pixy))) /
11247 vp.view_scale_ppm;
11248 if (dist > 0.0) {
11249 // calculate GC distance between these two points in meters
11250 double distgc =
11251 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11252
11253 // If the distances are nonsense, it means that the scale is very
11254 // small and the segment wrapped the world So skip it....
11255 // TODO improve this to draw two segments
11256 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11257 b_skip = true;
11258 } else
11259 b_skip = true;
11260 }
11261
11262 ClipResult res = cohen_sutherland_line_clip_i(
11263 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11264 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11265
11266 plylat = plylat1;
11267 plylon = plylon1;
11268 pixx = pixxs1;
11269 pixy = pixys1;
11270 }
11271
11272 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11273 GetCanvasPointPix(plylat1, plylon1, &r1);
11274 pixx1 = r1.x;
11275 pixy1 = r1.y;
11276
11277 ClipResult res = cohen_sutherland_line_clip_i(
11278 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11279 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11280 }
11281 }
11282}
11283
11284static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
11285 const wxString &second) {
11286 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11287
11288 int pointsize = dFont->GetPointSize();
11289 pointsize /= OCPN_GetWinDIPScaleFactor();
11290
11291 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11292 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11293 false, dFont->GetFaceName());
11294
11295 dc.SetFont(*psRLI_font);
11296
11297 int w1, h1;
11298 int w2 = 0;
11299 int h2 = 0;
11300 int h, w;
11301
11302 int xp, yp;
11303 int hilite_offset = 3;
11304#ifdef __WXMAC__
11305 wxScreenDC sdc;
11306 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
11307 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
11308#else
11309 dc.GetTextExtent(first, &w1, &h1);
11310 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
11311#endif
11312
11313 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11314 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11315
11316 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
11317 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11318
11319 h = h1 + h2;
11320
11321 xp = ref_point.x - w;
11322 yp = ref_point.y;
11323 yp += hilite_offset;
11324
11325 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor(_T ( "YELO1" )), 172);
11326
11327 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" ))));
11328 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
11329
11330 dc.DrawText(first, xp, yp);
11331 if (second.Len()) dc.DrawText(second, xp, yp + h1);
11332}
11333
11334void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11335 if (!g_bAllowShipToActive) return;
11336
11337 Route *rt = g_pRouteMan->GetpActiveRoute();
11338 if (!rt) return;
11339
11340 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11341 wxPoint2DDouble pa, pb;
11342 GetDoubleCanvasPointPix(gLat, gLon, &pa);
11343 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11344
11345 // set pen
11346 int width =
11347 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11348 if (rt->m_width != wxPENSTYLE_INVALID)
11349 width = rt->m_width; // set route pen style if any
11350 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11351 g_shipToActiveStyle, 5)]; // get setting pen style
11352 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11353 wxColour color =
11354 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11355 : // set setting route pen color
11356 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11357 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11358
11359 dc.SetPen(*mypen);
11360 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11361
11362 if (!Use_Opengl)
11363 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11364 (int)pb.m_y, GetVP(), true);
11365
11366#ifdef ocpnUSE_GL
11367 else {
11368#ifdef USE_ANDROID_GLES2
11369 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11370#else
11371 if (style != wxPENSTYLE_SOLID) {
11372 if (glChartCanvas::dash_map.find(style) !=
11373 glChartCanvas::dash_map.end()) {
11374 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11375 dc.SetPen(*mypen);
11376 }
11377 }
11378 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11379#endif
11380
11381 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11382 (int)pb.m_x, (int)pb.m_y, GetVP());
11383 }
11384#endif
11385 }
11386}
11387
11388void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11389 Route *route = 0;
11390 if (m_routeState >= 2) route = m_pMouseRoute;
11391 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11392 route = m_pMeasureRoute;
11393
11394 if (!route) return;
11395
11396 // Validate route pointer
11397 if (!g_pRouteMan->IsRouteValid(route)) return;
11398
11399 double render_lat = m_cursor_lat;
11400 double render_lon = m_cursor_lon;
11401
11402 int np = route->GetnPoints();
11403 if (np) {
11404 if (g_btouch && (np > 1)) np--;
11405 RoutePoint rp = route->GetPoint(np);
11406 render_lat = rp.m_lat;
11407 render_lon = rp.m_lon;
11408 }
11409
11410 double rhumbBearing, rhumbDist;
11411 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11412 &rhumbBearing, &rhumbDist);
11413 double brg = rhumbBearing;
11414 double dist = rhumbDist;
11415
11416 // Skip GreatCircle rubberbanding on touch devices.
11417 if (!g_btouch) {
11418 double gcBearing, gcBearing2, gcDist;
11419 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11420 m_cursor_lat, &gcDist, &gcBearing,
11421 &gcBearing2);
11422 double gcDistm = gcDist / 1852.0;
11423
11424 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11425 rhumbBearing = 90.;
11426
11427 wxPoint destPoint, lastPoint;
11428
11429 route->m_NextLegGreatCircle = false;
11430 int milesDiff = rhumbDist - gcDistm;
11431 if (milesDiff > 1) {
11432 brg = gcBearing;
11433 dist = gcDistm;
11434 route->m_NextLegGreatCircle = true;
11435 }
11436
11437 // FIXME (MacOS, the first segment is rendered wrong)
11438 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11439 &lastPoint);
11440
11441 if (route->m_NextLegGreatCircle) {
11442 for (int i = 1; i <= milesDiff; i++) {
11443 double p = (double)i * (1.0 / (double)milesDiff);
11444 double pLat, pLon;
11445 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11446 &pLon, &pLat, &gcBearing2);
11447 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11448 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11449 false);
11450 lastPoint = destPoint;
11451 }
11452 } else {
11453 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11454 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11455 false);
11456 if (m_bMeasure_DistCircle) {
11457 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11458 powf((float)(r_rband.y - lastPoint.y), 2));
11459
11460 dc.SetPen(*g_pRouteMan->GetRoutePen());
11461 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11462 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11463 }
11464 }
11465 }
11466 }
11467
11468 wxString routeInfo;
11469 double varBrg = 0;
11470 if (g_bShowTrue)
11471 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11472 0x00B0);
11473
11474 if (g_bShowMag) {
11475 double latAverage = (m_cursor_lat + render_lat) / 2;
11476 double lonAverage = (m_cursor_lon + render_lon) / 2;
11477 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11478
11479 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11480 (int)varBrg, 0x00B0);
11481 }
11482 routeInfo << " " << FormatDistanceAdaptive(dist);
11483
11484 // To make it easier to use a route as a bearing on a charted object add for
11485 // the first leg also the reverse bearing.
11486 if (np == 1) {
11487 routeInfo << "\nReverse: ";
11488 if (g_bShowTrue)
11489 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11490 (int)(brg + 180.) % 360, 0x00B0);
11491 if (g_bShowMag)
11492 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11493 (int)(varBrg + 180.) % 360, 0x00B0);
11494 }
11495
11496 wxString s0;
11497 if (!route->m_bIsInLayer)
11498 s0.Append(_("Route") + ": ");
11499 else
11500 s0.Append(_("Layer Route: "));
11501
11502 double disp_length = route->m_route_length;
11503 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11504 s0 += FormatDistanceAdaptive(disp_length);
11505
11506 RouteLegInfo(dc, r_rband, routeInfo, s0);
11507
11508 m_brepaint_piano = true;
11509}
11510
11511void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11512 if (!m_bShowVisibleSectors) return;
11513
11514 if (g_bDeferredInitDone) {
11515 // need to re-evaluate sectors?
11516 double rhumbBearing, rhumbDist;
11517 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11518 &rhumbBearing, &rhumbDist);
11519
11520 if (rhumbDist > 0.05) // miles
11521 {
11522 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11523 m_sectorlegsVisible);
11524 m_sector_glat = gLat;
11525 m_sector_glon = gLon;
11526 }
11527 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11528 }
11529}
11530
11531void ChartCanvas::WarpPointerDeferred(int x, int y) {
11532 warp_x = x;
11533 warp_y = y;
11534 warp_flag = true;
11535}
11536
11537int s_msg;
11538
11539void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11540 if (!ps52plib) return;
11541
11542 if (VPoint.b_quilt) { // quilted
11543 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11544
11545 if (m_pQuilt->IsQuiltVector()) {
11546 if (ps52plib->GetStateHash() != m_s52StateHash) {
11547 UpdateS52State();
11548 m_s52StateHash = ps52plib->GetStateHash();
11549 }
11550 }
11551 } else {
11552 if (ps52plib->GetStateHash() != m_s52StateHash) {
11553 UpdateS52State();
11554 m_s52StateHash = ps52plib->GetStateHash();
11555 }
11556 }
11557
11558 // Plugin charts
11559 bool bSendPlibState = true;
11560 if (VPoint.b_quilt) { // quilted
11561 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11562 }
11563
11564 if (bSendPlibState) {
11565 wxJSONValue v;
11566 v["OpenCPN Version Major"] = VERSION_MAJOR;
11567 v["OpenCPN Version Minor"] = VERSION_MINOR;
11568 v["OpenCPN Version Patch"] = VERSION_PATCH;
11569 v["OpenCPN Version Date"] = VERSION_DATE;
11570 v["OpenCPN Version Full"] = VERSION_FULL;
11571
11572 // S52PLIB state
11573 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11574 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11575 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11576 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11577 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11578 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11579 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11580
11581 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11582
11583 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11584 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11585
11586 // Global S52 options
11587
11588 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11589 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11590 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11591 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11592 ps52plib->m_bShowS57ImportantTextOnly;
11593 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11594 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11595 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11596 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11597 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11598
11599 // Some global GUI parameters, for completeness
11600 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11601 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11602 v["OpenCPN Scale Factor Exp"] =
11603 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11604 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11605
11606 wxJSONWriter w;
11607 wxString out;
11608 w.Write(v, out);
11609
11610 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11611 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11612 g_lastS52PLIBPluginMessage = out;
11613 }
11614 }
11615}
11616int spaint;
11617int s_in_update;
11618void ChartCanvas::OnPaint(wxPaintEvent &event) {
11619 wxPaintDC dc(this);
11620
11621 // GetToolbar()->Show( m_bToolbarEnable );
11622
11623 // Paint updates may have been externally disabled (temporarily, to avoid
11624 // Yield() recursion performance loss) It is important that the wxPaintDC is
11625 // built, even if we elect to not process this paint message. Otherwise, the
11626 // paint message may not be removed from the message queue, esp on Windows.
11627 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11628
11629 if (!m_b_paint_enable) {
11630 return;
11631 }
11632
11633 // If necessary, reconfigure the S52 PLIB
11634 UpdateCanvasS52PLIBConfig();
11635
11636#ifdef ocpnUSE_GL
11637 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11638
11639 if (m_glcc && g_bopengl) {
11640 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11641 s_in_update++;
11642 m_glcc->Update();
11643 s_in_update--;
11644 }
11645
11646 return;
11647 }
11648#endif
11649
11650 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11651
11652 wxRegion ru = GetUpdateRegion();
11653
11654 int rx, ry, rwidth, rheight;
11655 ru.GetBox(rx, ry, rwidth, rheight);
11656 // printf("%d Onpaint update region box: %d %d %d %d\n", spaint++, rx, ry,
11657 // rwidth, rheight);
11658
11659#ifdef ocpnUSE_DIBSECTION
11660 ocpnMemDC temp_dc;
11661#else
11662 wxMemoryDC temp_dc;
11663#endif
11664
11665 long height = GetVP().pix_height;
11666
11667#ifdef __WXMAC__
11668 // On OS X we have to explicitly extend the region for the piano area
11669 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11670 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11671 height += m_Piano->GetHeight();
11672#endif // __WXMAC__
11673 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11674
11675 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11676 if (pthumbwin) {
11677 int thumbx, thumby, thumbsx, thumbsy;
11678 pthumbwin->GetPosition(&thumbx, &thumby);
11679 pthumbwin->GetSize(&thumbsx, &thumbsy);
11680 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11681
11682 if (pthumbwin->IsShown()) {
11683 rgn_chart.Subtract(rgn_thumbwin);
11684 ru.Subtract(rgn_thumbwin);
11685 }
11686 }
11687
11688 // subtract the chart bar if it isn't transparent, and determine if we need to
11689 // paint it
11690 wxRegion rgn_blit = ru;
11691 if (g_bShowChartBar) {
11692 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11693 GetClientSize().x, m_Piano->GetHeight());
11694
11695 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11696 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11697 if (style->chartStatusWindowTransparent)
11698 m_brepaint_piano = true;
11699 else
11700 ru.Subtract(chart_bar_rect);
11701 }
11702 }
11703
11704 if (m_Compass && m_Compass->IsShown()) {
11705 wxRect compassRect = m_Compass->GetRect();
11706 if (ru.Contains(compassRect) != wxOutRegion) {
11707 ru.Subtract(compassRect);
11708 }
11709 }
11710
11711 wxRect noteRect = m_notification_button->GetRect();
11712 if (ru.Contains(noteRect) != wxOutRegion) {
11713 ru.Subtract(noteRect);
11714 }
11715
11716 // Is this viewpoint the same as the previously painted one?
11717 bool b_newview = true;
11718
11719 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11720 (m_cache_vp.rotation == VPoint.rotation) &&
11721 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11722 m_cache_vp.IsValid()) {
11723 b_newview = false;
11724 }
11725
11726 // If the ViewPort is skewed or rotated, we may be able to use the cached
11727 // rotated bitmap.
11728 bool b_rcache_ok = false;
11729 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11730 b_rcache_ok = !b_newview;
11731
11732 // Make a special VP
11733 if (VPoint.b_MercatorProjectionOverride)
11734 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11735 ViewPort svp = VPoint;
11736
11737 svp.pix_width = svp.rv_rect.width;
11738 svp.pix_height = svp.rv_rect.height;
11739
11740 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11741 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11742 // VPoint.rv_rect.height);
11743
11744 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11745
11746 // If we are going to use the cached rotated image, there is no need to fetch
11747 // any chart data and this will do it...
11748 if (b_rcache_ok) chart_get_region.Clear();
11749
11750 // Blit pan acceleration
11751 if (VPoint.b_quilt) // quilted
11752 {
11753 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11754
11755 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11756
11757 bool busy = false;
11758 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11759 m_cache_vp.rotation != VPoint.rotation)) {
11760 AbstractPlatform::ShowBusySpinner();
11761 busy = true;
11762 }
11763
11764 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11765 (m_working_bm.GetHeight() != svp.pix_height))
11766 m_working_bm.Create(svp.pix_width, svp.pix_height,
11767 -1); // make sure the target is big enoug
11768
11769 if (fabs(VPoint.rotation) < 0.01) {
11770 bool b_save = true;
11771
11772 if (g_SencThreadManager) {
11773 if (g_SencThreadManager->GetJobCount()) {
11774 b_save = false;
11775 m_cache_vp.Invalidate();
11776 }
11777 }
11778
11779 // If the saved wxBitmap from last OnPaint is useable
11780 // calculate the blit parameters
11781
11782 // We can only do screen blit painting if subsequent ViewPorts differ by
11783 // whole pixels So, in small scale bFollow mode, force the full screen
11784 // render. This seems a hack....There may be better logic here.....
11785
11786 // if(m_bFollow)
11787 // b_save = false;
11788
11789 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11790 if (b_newview) {
11791 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11792 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11793
11794 int dy = c_new.y - c_old.y;
11795 int dx = c_new.x - c_old.x;
11796
11797 // printf("In OnPaint Trying Blit dx: %d
11798 // dy:%d\n\n", dx, dy);
11799
11800 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11801 if (dx || dy) {
11802 // Blit the reuseable portion of the cached wxBitmap to a working
11803 // bitmap
11804 temp_dc.SelectObject(m_working_bm);
11805
11806 wxMemoryDC cache_dc;
11807 cache_dc.SelectObject(m_cached_chart_bm);
11808
11809 if (dy > 0) {
11810 if (dx > 0) {
11811 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11812 VPoint.pix_height - dy, &cache_dc, dx, dy);
11813 } else {
11814 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11815 VPoint.pix_height - dy, &cache_dc, 0, dy);
11816 }
11817
11818 } else {
11819 if (dx > 0) {
11820 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11821 VPoint.pix_height + dy, &cache_dc, dx, 0);
11822 } else {
11823 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11824 VPoint.pix_height + dy, &cache_dc, 0, 0);
11825 }
11826 }
11827
11828 OCPNRegion update_region;
11829 if (dy) {
11830 if (dy > 0)
11831 update_region.Union(
11832 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11833 else
11834 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11835 }
11836
11837 if (dx) {
11838 if (dx > 0)
11839 update_region.Union(
11840 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11841 else
11842 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11843 }
11844
11845 // Render the new region
11846 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11847 update_region);
11848 cache_dc.SelectObject(wxNullBitmap);
11849 } else {
11850 // No sensible (dx, dy) change in the view, so use the cached
11851 // member bitmap
11852 temp_dc.SelectObject(m_cached_chart_bm);
11853 b_save = false;
11854 }
11855 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11856
11857 } else // not blitable
11858 {
11859 temp_dc.SelectObject(m_working_bm);
11860 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11861 chart_get_region);
11862 }
11863 } else {
11864 // No change in the view, so use the cached member bitmap2
11865 temp_dc.SelectObject(m_cached_chart_bm);
11866 b_save = false;
11867 }
11868 } else // cached bitmap is not yet valid
11869 {
11870 temp_dc.SelectObject(m_working_bm);
11871 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11872 chart_get_region);
11873 }
11874
11875 // Save the fully rendered quilt image as a wxBitmap member of this class
11876 if (b_save) {
11877 // if((m_cached_chart_bm.GetWidth() !=
11878 // svp.pix_width) ||
11879 // (m_cached_chart_bm.GetHeight() !=
11880 // svp.pix_height))
11881 // m_cached_chart_bm.Create(svp.pix_width,
11882 // svp.pix_height, -1); // target wxBitmap
11883 // is big enough
11884 wxMemoryDC scratch_dc_0;
11885 scratch_dc_0.SelectObject(m_cached_chart_bm);
11886 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11887
11888 scratch_dc_0.SelectObject(wxNullBitmap);
11889
11890 m_bm_cache_vp =
11891 VPoint; // save the ViewPort associated with the cached wxBitmap
11892 }
11893 }
11894
11895 else // quilted, rotated
11896 {
11897 temp_dc.SelectObject(m_working_bm);
11898 OCPNRegion chart_get_all_region(
11899 wxRect(0, 0, svp.pix_width, svp.pix_height));
11900 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11901 chart_get_all_region);
11902 }
11903
11904 AbstractPlatform::HideBusySpinner();
11905
11906 }
11907
11908 else // not quilted
11909 {
11910 if (!m_singleChart) {
11911 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
11912 dc.Clear();
11913 return;
11914 }
11915
11916 if (!chart_get_region.IsEmpty()) {
11917 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
11918 }
11919 }
11920
11921 if (temp_dc.IsOk()) {
11922 // Arrange to render the World Chart vector data behind the rendered
11923 // current chart so that uncovered canvas areas show at least the world
11924 // chart.
11925 OCPNRegion chartValidRegion;
11926 if (!VPoint.b_quilt) {
11927 // Make a region covering the current chart on the canvas
11928
11929 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
11930 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
11931 else {
11932 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
11933 // require that the viewport passed here have pix_width and pix_height
11934 // set to the actual display, not the virtual (rv_rect) sizes
11935 // (the vector calculations require the virtual sizes in svp)
11936
11937 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
11938 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
11939 }
11940 } else
11941 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
11942
11943 temp_dc.DestroyClippingRegion();
11944
11945 // Copy current chart region
11946 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
11947
11948 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
11949
11950 if (!backgroundRegion.IsEmpty()) {
11951 // Draw the Background Chart only in the areas NOT covered by the
11952 // current chart view
11953
11954 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
11955 clipping regions with more than 1 rectangle so... */
11956 wxColour water = pWorldBackgroundChart->water;
11957 if (water.IsOk()) {
11958 temp_dc.SetPen(*wxTRANSPARENT_PEN);
11959 temp_dc.SetBrush(wxBrush(water));
11960 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
11961 while (upd.HaveRects()) {
11962 wxRect rect = upd.GetRect();
11963 temp_dc.DrawRectangle(rect);
11964 upd.NextRect();
11965 }
11966 }
11967 // Associate with temp_dc
11968 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
11969 temp_dc.SetDeviceClippingRegion(*clip_region);
11970 delete clip_region;
11971
11972 ocpnDC bgdc(temp_dc);
11973 double r = VPoint.rotation;
11974 SetVPRotation(VPoint.skew);
11975
11976 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
11977 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
11978
11979 SetVPRotation(r);
11980 }
11981 } // temp_dc.IsOk();
11982
11983 wxMemoryDC *pChartDC = &temp_dc;
11984 wxMemoryDC rotd_dc;
11985
11986 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
11987 // Can we use the current rotated image cache?
11988 if (!b_rcache_ok) {
11989#ifdef __WXMSW__
11990 wxMemoryDC tbase_dc;
11991 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
11992 tbase_dc.SelectObject(bm_base);
11993 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11994 tbase_dc.SelectObject(wxNullBitmap);
11995#else
11996 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
11997#endif
11998
11999 wxImage base_image;
12000 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12001
12002 // Use a local static image rotator to improve wxWidgets code profile
12003 // Especially, on GTK the wxRound and wxRealPoint functions are very
12004 // expensive.....
12005
12006 double angle = GetVP().skew - GetVP().rotation;
12007 wxImage ri;
12008 bool b_rot_ok = false;
12009 if (base_image.IsOk()) {
12010 ViewPort rot_vp = GetVP();
12011
12012 m_b_rot_hidef = false;
12013
12014 ri = Image_Rotate(
12015 base_image, angle,
12016 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12017 m_b_rot_hidef, &m_roffset);
12018
12019 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12020 (rot_vp.rotation == VPoint.rotation) &&
12021 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12022 rot_vp.IsValid() && (ri.IsOk())) {
12023 b_rot_ok = true;
12024 }
12025 }
12026
12027 if (b_rot_ok) {
12028 delete m_prot_bm;
12029 m_prot_bm = new wxBitmap(ri);
12030 }
12031
12032 m_roffset.x += VPoint.rv_rect.x;
12033 m_roffset.y += VPoint.rv_rect.y;
12034 }
12035
12036 if (m_prot_bm && m_prot_bm->IsOk()) {
12037 rotd_dc.SelectObject(*m_prot_bm);
12038 pChartDC = &rotd_dc;
12039 } else {
12040 pChartDC = &temp_dc;
12041 m_roffset = wxPoint(0, 0);
12042 }
12043 } else { // unrotated
12044 pChartDC = &temp_dc;
12045 m_roffset = wxPoint(0, 0);
12046 }
12047
12048 wxPoint offset = m_roffset;
12049
12050 // Save the PixelCache viewpoint for next time
12051 m_cache_vp = VPoint;
12052
12053 // Set up a scratch DC for overlay objects
12054 wxMemoryDC mscratch_dc;
12055 mscratch_dc.SelectObject(*pscratch_bm);
12056
12057 mscratch_dc.ResetBoundingBox();
12058 mscratch_dc.DestroyClippingRegion();
12059 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12060
12061 // Blit the externally invalidated areas of the chart onto the scratch dc
12062 wxRegionIterator upd(rgn_blit); // get the update rect list
12063 while (upd) {
12064 wxRect rect = upd.GetRect();
12065
12066 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12067 rect.x - offset.x, rect.y - offset.y);
12068 upd++;
12069 }
12070
12071 // If multi-canvas, indicate which canvas has keyboard focus
12072 // by drawing a simple blue bar at the top.
12073 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12074 if (this == wxWindow::FindFocus()) {
12075 g_focusCanvas = this;
12076
12077 wxColour colour = GetGlobalColor("BLUE4");
12078 mscratch_dc.SetPen(wxPen(colour));
12079 mscratch_dc.SetBrush(wxBrush(colour));
12080
12081 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12082 mscratch_dc.DrawRectangle(activeRect);
12083 }
12084 }
12085
12086 // Any MBtiles?
12087 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12088 unsigned int im = stackIndexArray.size();
12089 if (VPoint.b_quilt && im > 0) {
12090 std::vector<int> tiles_to_show;
12091 for (unsigned int is = 0; is < im; is++) {
12092 const ChartTableEntry &cte =
12093 ChartData->GetChartTableEntry(stackIndexArray[is]);
12094 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12095 continue;
12096 }
12097 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12098 tiles_to_show.push_back(stackIndexArray[is]);
12099 }
12100 }
12101
12102 if (tiles_to_show.size())
12103 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12104 }
12105
12106 // May get an unexpected OnPaint call while switching display modes
12107 // Guard for that.
12108 if (!g_bopengl) {
12109 ocpnDC scratch_dc(mscratch_dc);
12110 RenderAlertMessage(mscratch_dc, GetVP());
12111 }
12112
12113#if 0
12114 // quiting?
12115 if (g_bquiting) {
12116#ifdef ocpnUSE_DIBSECTION
12117 ocpnMemDC q_dc;
12118#else
12119 wxMemoryDC q_dc;
12120#endif
12121 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12122 q_dc.SelectObject(qbm);
12123
12124 // Get a copy of the screen
12125 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12126
12127 // Draw a rectangle over the screen with a stipple brush
12128 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12129 q_dc.SetBrush(qbr);
12130 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12131
12132 // Blit back into source
12133 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12134 wxCOPY);
12135
12136 q_dc.SelectObject(wxNullBitmap);
12137 }
12138#endif
12139
12140#if 0
12141 // It is possible that this two-step method may be reuired for some platforms.
12142 // So, retain in the code base to aid recovery if necessary
12143
12144 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12145 if( VPoint.b_quilt ) {
12146 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12147 ChartBase *chart = m_pQuilt->GetRefChart();
12148 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12149
12150 // Clear the text Global declutter list
12151 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12152 if(ChPI)
12153 ChPI->ClearPLIBTextList();
12154 else{
12155 if(ps52plib)
12156 ps52plib->ClearTextList();
12157 }
12158
12159 wxMemoryDC t_dc;
12160 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12161
12162 wxColor maskBackground = wxColour(1,0,0);
12163 t_dc.SelectObject( qbm );
12164 t_dc.SetBackground(wxBrush(maskBackground));
12165 t_dc.Clear();
12166
12167 // Copy the scratch DC into the new bitmap
12168 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12169
12170 // Render the text to the new bitmap
12171 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12172 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12173
12174 // Copy the new bitmap back to the scratch dc
12175 wxRegionIterator upd_final( ru );
12176 while( upd_final ) {
12177 wxRect rect = upd_final.GetRect();
12178 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12179 upd_final++;
12180 }
12181
12182 t_dc.SelectObject( wxNullBitmap );
12183 }
12184 }
12185 }
12186#endif
12187 // Direct rendering model...
12188 if (VPoint.b_quilt) {
12189 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12190 ChartBase *chart = m_pQuilt->GetRefChart();
12191 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12192 // Clear the text Global declutter list
12193 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12194 if (ChPI)
12195 ChPI->ClearPLIBTextList();
12196 else {
12197 if (ps52plib) ps52plib->ClearTextList();
12198 }
12199
12200 // Render the text directly to the scratch bitmap
12201 OCPNRegion chart_all_text_region(
12202 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12203
12204 if (g_bShowChartBar && m_Piano) {
12205 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12206 GetVP().pix_width, m_Piano->GetHeight());
12207
12208 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12209 if (!style->chartStatusWindowTransparent)
12210 chart_all_text_region.Subtract(chart_bar_rect);
12211 }
12212
12213 if (m_Compass && m_Compass->IsShown()) {
12214 wxRect compassRect = m_Compass->GetRect();
12215 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12216 chart_all_text_region.Subtract(compassRect);
12217 }
12218 }
12219
12220 mscratch_dc.DestroyClippingRegion();
12221
12222 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12223 chart_all_text_region);
12224 }
12225 }
12226 }
12227
12228 // Now that charts are fully rendered, apply the overlay objects as decals.
12229 ocpnDC scratch_dc(mscratch_dc);
12230 DrawOverlayObjects(scratch_dc, ru);
12231
12232 // And finally, blit the scratch dc onto the physical dc
12233 wxRegionIterator upd_final(rgn_blit);
12234 while (upd_final) {
12235 wxRect rect = upd_final.GetRect();
12236 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12237 rect.y);
12238 upd_final++;
12239 }
12240
12241 // Deselect the chart bitmap from the temp_dc, so that it will not be
12242 // destroyed in the temp_dc dtor
12243 temp_dc.SelectObject(wxNullBitmap);
12244 // And for the scratch bitmap
12245 mscratch_dc.SelectObject(wxNullBitmap);
12246
12247 dc.DestroyClippingRegion();
12248
12249 PaintCleanup();
12250}
12251
12252void ChartCanvas::PaintCleanup() {
12253 // Handle the current graphic window, if present
12254
12255 if (pCwin) {
12256 pCwin->Show();
12257 if (m_bTCupdate) {
12258 pCwin->Refresh();
12259 pCwin->Update();
12260 }
12261 }
12262
12263 // And set flags for next time
12264 m_bTCupdate = false;
12265
12266 // Handle deferred WarpPointer
12267 if (warp_flag) {
12268 WarpPointer(warp_x, warp_y);
12269 warp_flag = false;
12270 }
12271
12272 // Start movement timers, this runs nearly immediately.
12273 // the reason we cannot simply call it directly is the
12274 // refresh events it emits may be blocked from this paint event
12275 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12276 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12277}
12278
12279#if 0
12280wxColour GetErrorGraphicColor(double val)
12281{
12282 /*
12283 double valm = wxMin(val_max, val);
12284
12285 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12286 unsigned char red = (unsigned char)(255 * (valm/val_max));
12287
12288 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12289
12290 hv.saturation = 1.0;
12291 hv.value = 1.0;
12292
12293 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12294 return wxColour(rv.red, rv.green, rv.blue);
12295 */
12296
12297 // HTML colors taken from NOAA WW3 Web representation
12298 wxColour c;
12299 if((val > 0) && (val < 1)) c.Set("#002ad9");
12300 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12301 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12302 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12303 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12304 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12305 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12306 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12307 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12308 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12309 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12310 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12311 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12312 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12313 else if((val >= 30) && (val < 36)) c.Set("#870000");
12314 else if((val >= 36) && (val < 42)) c.Set("#690000");
12315 else if((val >= 42) && (val < 48)) c.Set("#550000");
12316 else if( val >= 48) c.Set("#410000");
12317
12318 return c;
12319}
12320
12321void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12322{
12323 wxImage gr_image(vp->pix_width, vp->pix_height);
12324 gr_image.InitAlpha();
12325
12326 double maxval = -10000;
12327 double minval = 10000;
12328
12329 double rlat, rlon;
12330 double glat, glon;
12331
12332 GetCanvasPixPoint(0, 0, rlat, rlon);
12333
12334 for(int i=1; i < vp->pix_height-1; i++)
12335 {
12336 for(int j=0; j < vp->pix_width; j++)
12337 {
12338 // Reference mercator value
12339// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12340
12341 // Georef value
12342 GetCanvasPixPoint(j, i, glat, glon);
12343
12344 maxval = wxMax(maxval, (glat - rlat));
12345 minval = wxMin(minval, (glat - rlat));
12346
12347 }
12348 rlat = glat;
12349 }
12350
12351 GetCanvasPixPoint(0, 0, rlat, rlon);
12352 for(int i=1; i < vp->pix_height-1; i++)
12353 {
12354 for(int j=0; j < vp->pix_width; j++)
12355 {
12356 // Reference mercator value
12357// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12358
12359 // Georef value
12360 GetCanvasPixPoint(j, i, glat, glon);
12361
12362 double f = ((glat - rlat)-minval)/(maxval - minval);
12363
12364 double dy = (f * 40);
12365
12366 wxColour c = GetErrorGraphicColor(dy);
12367 unsigned char r = c.Red();
12368 unsigned char g = c.Green();
12369 unsigned char b = c.Blue();
12370
12371 gr_image.SetRGB(j, i, r,g,b);
12372 if((glat - rlat )!= 0)
12373 gr_image.SetAlpha(j, i, 128);
12374 else
12375 gr_image.SetAlpha(j, i, 255);
12376
12377 }
12378 rlat = glat;
12379 }
12380
12381 // Create a Bitmap
12382 wxBitmap *pbm = new wxBitmap(gr_image);
12383 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12384 pbm->SetMask(gr_mask);
12385
12386 pmdc->DrawBitmap(*pbm, 0,0);
12387
12388 delete pbm;
12389
12390}
12391
12392#endif
12393
12394void ChartCanvas::CancelMouseRoute() {
12395 m_routeState = 0;
12396 m_pMouseRoute = NULL;
12397 m_bDrawingRoute = false;
12398}
12399
12400int ChartCanvas::GetNextContextMenuId() {
12401 return CanvasMenuHandler::GetNextContextMenuId();
12402}
12403
12404bool ChartCanvas::SetCursor(const wxCursor &c) {
12405#ifdef ocpnUSE_GL
12406 if (g_bopengl && m_glcc)
12407 return m_glcc->SetCursor(c);
12408 else
12409#endif
12410 return wxWindow::SetCursor(c);
12411}
12412
12413void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12414 if (g_bquiting) return;
12415 // Keep the mouse position members up to date
12416 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12417
12418 // Retrigger the route leg popup timer
12419 // This handles the case when the chart is moving in auto-follow mode,
12420 // but no user mouse input is made. The timer handler may Hide() the
12421 // popup if the chart moved enough n.b. We use slightly longer oneshot
12422 // value to allow this method's Refresh() to complete before potentially
12423 // getting another Refresh() in the popup timer handler.
12424 if (!m_RolloverPopupTimer.IsRunning() &&
12425 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12426 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12427 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12428 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12429
12430#ifdef ocpnUSE_GL
12431 if (m_glcc && g_bopengl) {
12432 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12433 // overlay objects.
12434 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12435
12436 m_glcc->Refresh(eraseBackground,
12437 NULL); // We always are going to render the entire screen
12438 // anyway, so make
12439 // sure that the window managers understand the invalid area
12440 // is actually the entire client area.
12441
12442 // We need to selectively Refresh some child windows, if they are visible.
12443 // Note that some children are refreshed elsewhere on timer ticks, so don't
12444 // need attention here.
12445
12446 // Thumbnail chart
12447 if (pthumbwin && pthumbwin->IsShown()) {
12448 pthumbwin->Raise();
12449 pthumbwin->Refresh(false);
12450 }
12451
12452 // ChartInfo window
12453 if (m_pCIWin && m_pCIWin->IsShown()) {
12454 m_pCIWin->Raise();
12455 m_pCIWin->Refresh(false);
12456 }
12457
12458 // if(g_MainToolbar)
12459 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12460
12461 } else
12462#endif
12463 wxWindow::Refresh(eraseBackground, rect);
12464}
12465
12466void ChartCanvas::Update() {
12467 if (m_glcc && g_bopengl) {
12468#ifdef ocpnUSE_GL
12469 m_glcc->Update();
12470#endif
12471 } else
12472 wxWindow::Update();
12473}
12474
12475void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12476 if (!pemboss) return;
12477 int x = pemboss->x, y = pemboss->y;
12478 const double factor = 200;
12479
12480 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12481 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12482 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12483
12484 // Grab a snipped image out of the chart
12485 wxMemoryDC snip_dc;
12486 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12487 snip_dc.SelectObject(snip_bmp);
12488
12489 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12490 snip_dc.SelectObject(wxNullBitmap);
12491
12492 wxImage snip_img = snip_bmp.ConvertToImage();
12493
12494 // Apply Emboss map to the snip image
12495 unsigned char *pdata = snip_img.GetData();
12496 if (pdata) {
12497 for (int y = 0; y < pemboss->height; y++) {
12498 int map_index = (y * pemboss->width);
12499 for (int x = 0; x < pemboss->width; x++) {
12500 double val = (pemboss->pmap[map_index] * factor) / 256.;
12501
12502 int nred = (int)((*pdata) + val);
12503 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12504 *pdata++ = (unsigned char)nred;
12505
12506 int ngreen = (int)((*pdata) + val);
12507 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12508 *pdata++ = (unsigned char)ngreen;
12509
12510 int nblue = (int)((*pdata) + val);
12511 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12512 *pdata++ = (unsigned char)nblue;
12513
12514 map_index++;
12515 }
12516 }
12517 }
12518
12519 // Convert embossed snip to a bitmap
12520 wxBitmap emb_bmp(snip_img);
12521
12522 // Map to another memoryDC
12523 wxMemoryDC result_dc;
12524 result_dc.SelectObject(emb_bmp);
12525
12526 // Blit to target
12527 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12528
12529 result_dc.SelectObject(wxNullBitmap);
12530}
12531
12532emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12533 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12534
12535 if (GetQuiltMode()) {
12536 // disable Overzoom indicator for MBTiles
12537 int refIndex = GetQuiltRefChartdbIndex();
12538 if (refIndex >= 0) {
12539 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12540 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12541 if (current_type == CHART_TYPE_MBTILES) {
12542 ChartBase *pChart = m_pQuilt->GetRefChart();
12543 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12544 if (ptc) {
12545 zoom_factor = ptc->GetZoomFactor();
12546 }
12547 }
12548 }
12549
12550 if (zoom_factor <= 3.9) return NULL;
12551 } else {
12552 if (m_singleChart) {
12553 if (zoom_factor <= 3.9) return NULL;
12554 } else
12555 return NULL;
12556 }
12557
12558 if (m_pEM_OverZoom) {
12559 m_pEM_OverZoom->x = 4;
12560 m_pEM_OverZoom->y = 0;
12561 if (g_MainToolbar && IsPrimaryCanvas()) {
12562 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12563 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12564 }
12565 }
12566 return m_pEM_OverZoom;
12567}
12568
12569void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12570 GridDraw(dc);
12571
12572 // bool pluginOverlayRender = true;
12573 //
12574 // if(g_canvasConfig > 0){ // Multi canvas
12575 // if(IsPrimaryCanvas())
12576 // pluginOverlayRender = false;
12577 // }
12578
12579 g_overlayCanvas = this;
12580
12581 if (g_pi_manager) {
12582 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12583 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12585 }
12586
12587 AISDrawAreaNotices(dc, GetVP(), this);
12588
12589 wxDC *pdc = dc.GetDC();
12590 if (pdc) {
12591 pdc->DestroyClippingRegion();
12592 wxDCClipper(*pdc, ru);
12593 }
12594
12595 if (m_bShowNavobjects) {
12596 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12597 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12598 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12599 DrawAnchorWatchPoints(dc);
12600 } else {
12601 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12602 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12603 }
12604
12605 AISDraw(dc, GetVP(), this);
12606 ShipDraw(dc);
12607 AlertDraw(dc);
12608
12609 RenderVisibleSectorLights(dc);
12610
12611 RenderAllChartOutlines(dc, GetVP());
12612 RenderRouteLegs(dc);
12613 RenderShipToActive(dc, false);
12614 ScaleBarDraw(dc);
12615 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12616 if (g_pi_manager) {
12617 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12619 }
12620
12621 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12622 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12623
12624 if (g_pi_manager) {
12625 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12627 }
12628
12629 if (m_bShowTide) {
12630 RebuildTideSelectList(GetVP().GetBBox());
12631 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12632 }
12633
12634 if (m_bShowCurrent) {
12635 RebuildCurrentSelectList(GetVP().GetBBox());
12636 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12637 }
12638
12639 if (!g_PrintingInProgress) {
12640 if (IsPrimaryCanvas()) {
12641 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12642 }
12643
12644 if (IsPrimaryCanvas()) {
12645 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12646 }
12647
12648 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12649
12650 if (m_pTrackRolloverWin) {
12651 m_pTrackRolloverWin->Draw(dc);
12652 m_brepaint_piano = true;
12653 }
12654
12655 if (m_pRouteRolloverWin) {
12656 m_pRouteRolloverWin->Draw(dc);
12657 m_brepaint_piano = true;
12658 }
12659
12660 if (m_pAISRolloverWin) {
12661 m_pAISRolloverWin->Draw(dc);
12662 m_brepaint_piano = true;
12663 }
12664 if (m_brepaint_piano && g_bShowChartBar) {
12665 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12666 }
12667
12668 if (m_Compass) m_Compass->Paint(dc);
12669
12670 if (!g_CanvasHideNotificationIcon) {
12671 auto &noteman = NotificationManager::GetInstance();
12672 if (noteman.GetNotificationCount()) {
12673 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12674 if (m_notification_button->UpdateStatus()) Refresh();
12675 m_notification_button->Show(true);
12676 m_notification_button->Paint(dc);
12677 } else {
12678 m_notification_button->Show(false);
12679 }
12680 }
12681 }
12682 if (g_pi_manager) {
12683 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12685 }
12686}
12687
12688emboss_data *ChartCanvas::EmbossDepthScale() {
12689 if (!m_bShowDepthUnits) return NULL;
12690
12691 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12692
12693 if (GetQuiltMode()) {
12694 wxString s = m_pQuilt->GetQuiltDepthUnit();
12695 s.MakeUpper();
12696 if (s == "FEET")
12697 depth_unit_type = DEPTH_UNIT_FEET;
12698 else if (s.StartsWith("FATHOMS"))
12699 depth_unit_type = DEPTH_UNIT_FATHOMS;
12700 else if (s.StartsWith("METERS"))
12701 depth_unit_type = DEPTH_UNIT_METERS;
12702 else if (s.StartsWith("METRES"))
12703 depth_unit_type = DEPTH_UNIT_METERS;
12704 else if (s.StartsWith("METRIC"))
12705 depth_unit_type = DEPTH_UNIT_METERS;
12706 else if (s.StartsWith("METER"))
12707 depth_unit_type = DEPTH_UNIT_METERS;
12708
12709 } else {
12710 if (m_singleChart) {
12711 depth_unit_type = m_singleChart->GetDepthUnitType();
12712 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12713 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12714 }
12715 }
12716
12717 emboss_data *ped = NULL;
12718 switch (depth_unit_type) {
12719 case DEPTH_UNIT_FEET:
12720 ped = m_pEM_Feet;
12721 break;
12722 case DEPTH_UNIT_METERS:
12723 ped = m_pEM_Meters;
12724 break;
12725 case DEPTH_UNIT_FATHOMS:
12726 ped = m_pEM_Fathoms;
12727 break;
12728 default:
12729 return NULL;
12730 }
12731
12732 ped->x = (GetVP().pix_width - ped->width);
12733
12734 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12735 wxRect r = m_Compass->GetRect();
12736 ped->y = r.y + r.height;
12737 } else {
12738 ped->y = 40;
12739 }
12740 return ped;
12741}
12742
12743void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12744 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12745 wxFont font;
12746 if (style->embossFont == wxEmptyString) {
12747 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12748 font = *dFont;
12749 font.SetPointSize(60);
12750 font.SetWeight(wxFONTWEIGHT_BOLD);
12751 } else
12752 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12753 wxFONTWEIGHT_BOLD, false, style->embossFont);
12754
12755 int emboss_width = 500;
12756 int emboss_height = 200;
12757
12758 // Free any existing emboss maps
12759 delete m_pEM_Feet;
12760 delete m_pEM_Meters;
12761 delete m_pEM_Fathoms;
12762
12763 // Create the 3 DepthUnit emboss map structures
12764 m_pEM_Feet =
12765 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12766 m_pEM_Meters =
12767 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12768 m_pEM_Fathoms =
12769 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12770}
12771
12772#define OVERZOOM_TEXT _("OverZoom")
12773
12774void ChartCanvas::SetOverzoomFont() {
12775 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12776 int w, h;
12777
12778 wxFont font;
12779 if (style->embossFont == wxEmptyString) {
12780 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12781 font = *dFont;
12782 font.SetPointSize(40);
12783 font.SetWeight(wxFONTWEIGHT_BOLD);
12784 } else
12785 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12786 wxFONTWEIGHT_BOLD, false, style->embossFont);
12787
12788 wxClientDC dc(this);
12789 dc.SetFont(font);
12790 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12791
12792 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12793 font.SetPointSize(font.GetPointSize() - 1);
12794 dc.SetFont(font);
12795 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12796 }
12797 m_overzoomFont = font;
12798 m_overzoomTextWidth = w;
12799 m_overzoomTextHeight = h;
12800}
12801
12802void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12803 delete m_pEM_OverZoom;
12804
12805 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12806 m_pEM_OverZoom =
12807 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12808 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12809}
12810
12811emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12812 int height, const wxString &str,
12813 ColorScheme cs) {
12814 int *pmap;
12815
12816 // Create a temporary bitmap
12817 wxBitmap bmp(width, height, -1);
12818
12819 // Create a memory DC
12820 wxMemoryDC temp_dc;
12821 temp_dc.SelectObject(bmp);
12822
12823 // Paint on it
12824 temp_dc.SetBackground(*wxWHITE_BRUSH);
12825 temp_dc.SetTextBackground(*wxWHITE);
12826 temp_dc.SetTextForeground(*wxBLACK);
12827
12828 temp_dc.Clear();
12829
12830 temp_dc.SetFont(font);
12831
12832 int str_w, str_h;
12833 temp_dc.GetTextExtent(str, &str_w, &str_h);
12834 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12835 temp_dc.DrawText(str, 1, 1);
12836
12837 // Deselect the bitmap
12838 temp_dc.SelectObject(wxNullBitmap);
12839
12840 // Convert bitmap the wxImage for manipulation
12841 wxImage img = bmp.ConvertToImage();
12842
12843 int image_width = str_w * 105 / 100;
12844 int image_height = str_h * 105 / 100;
12845 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12846 wxMin(image_height, img.GetHeight()));
12847 wxImage imgs = img.GetSubImage(r);
12848
12849 double val_factor;
12850 switch (cs) {
12851 case GLOBAL_COLOR_SCHEME_DAY:
12852 default:
12853 val_factor = 1;
12854 break;
12855 case GLOBAL_COLOR_SCHEME_DUSK:
12856 val_factor = .5;
12857 break;
12858 case GLOBAL_COLOR_SCHEME_NIGHT:
12859 val_factor = .25;
12860 break;
12861 }
12862
12863 int val;
12864 int index;
12865 const int w = imgs.GetWidth();
12866 const int h = imgs.GetHeight();
12867 pmap = (int *)calloc(w * h * sizeof(int), 1);
12868 // Create emboss map by differentiating the emboss image
12869 // and storing integer results in pmap
12870 // n.b. since the image is B/W, it is sufficient to check
12871 // one channel (i.e. red) only
12872 for (int y = 1; y < h - 1; y++) {
12873 for (int x = 1; x < w - 1; x++) {
12874 val =
12875 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
12876 val = (int)(val * val_factor);
12877 index = (y * w) + x;
12878 pmap[index] = val;
12879 }
12880 }
12881
12882 emboss_data *pret = new emboss_data;
12883 pret->pmap = pmap;
12884 pret->width = w;
12885 pret->height = h;
12886
12887 return pret;
12888}
12889
12890void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12891 Track *active_track = NULL;
12892 for (Track *pTrackDraw : g_TrackList) {
12893 if (g_pActiveTrack == pTrackDraw) {
12894 active_track = pTrackDraw;
12895 continue;
12896 }
12897
12898 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
12899 }
12900
12901 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12902}
12903
12904void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12905 Track *active_track = NULL;
12906 for (Track *pTrackDraw : g_TrackList) {
12907 if (g_pActiveTrack == pTrackDraw) {
12908 active_track = pTrackDraw;
12909 break;
12910 }
12911 }
12912 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12913}
12914
12915void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12916 Route *active_route = NULL;
12917
12918 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12919 node = node->GetNext()) {
12920 Route *pRouteDraw = node->GetData();
12921 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12922 active_route = pRouteDraw;
12923 continue;
12924 }
12925
12926 // if(m_canvasIndex == 1)
12927 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
12928 }
12929
12930 // Draw any active or selected route (or track) last, so that is is always on
12931 // top
12932 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12933}
12934
12935void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12936 Route *active_route = NULL;
12937
12938 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12939 node = node->GetNext()) {
12940 Route *pRouteDraw = node->GetData();
12941 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12942 active_route = pRouteDraw;
12943 break;
12944 }
12945 }
12946 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12947}
12948
12949void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12950 if (!pWayPointMan) return;
12951
12952 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12953
12954 while (node) {
12955 RoutePoint *pWP = node->GetData();
12956 if (pWP) {
12957 if (pWP->m_bIsInRoute) {
12958 node = node->GetNext();
12959 continue;
12960 }
12961
12962 /* technically incorrect... waypoint has bounding box */
12963 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
12964 RoutePointGui(*pWP).Draw(dc, this, NULL);
12965 else {
12966 // Are Range Rings enabled?
12967 if (pWP->GetShowWaypointRangeRings() &&
12968 (pWP->GetWaypointRangeRingsNumber() > 0)) {
12969 double factor = 1.00;
12970 if (pWP->GetWaypointRangeRingsStepUnits() ==
12971 1) // convert kilometers to NMi
12972 factor = 1 / 1.852;
12973
12974 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
12975 pWP->GetWaypointRangeRingsStep() / 60.;
12976 radius *= 2; // Fudge factor
12977
12978 LLBBox radar_box;
12979 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
12980 pWP->m_lat + radius, pWP->m_lon + radius);
12981 if (!BltBBox.IntersectOut(radar_box)) {
12982 RoutePointGui(*pWP).Draw(dc, this, NULL);
12983 }
12984 }
12985 }
12986 }
12987
12988 node = node->GetNext();
12989 }
12990}
12991
12992void ChartCanvas::DrawBlinkObjects(void) {
12993 // All RoutePoints
12994 wxRect update_rect;
12995
12996 if (!pWayPointMan) return;
12997
12998 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12999
13000 while (node) {
13001 RoutePoint *pWP = node->GetData();
13002 if (pWP) {
13003 if (pWP->m_bBlink) {
13004 update_rect.Union(pWP->CurrentRect_in_DC);
13005 }
13006 }
13007
13008 node = node->GetNext();
13009 }
13010 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13011}
13012
13013void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13014 // draw anchor watch rings, if activated
13015
13016 if (pAnchorWatchPoint1 || pAnchorWatchPoint2) {
13017 wxPoint r1, r2;
13018 wxPoint lAnchorPoint1, lAnchorPoint2;
13019 double lpp1 = 0.0;
13020 double lpp2 = 0.0;
13021 if (pAnchorWatchPoint1) {
13022 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13023 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
13024 &lAnchorPoint1);
13025 }
13026 if (pAnchorWatchPoint2) {
13027 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13028 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
13029 &lAnchorPoint2);
13030 }
13031
13032 wxPen ppPeng(GetGlobalColor(_T ( "UGREN" )), 2);
13033 wxPen ppPenr(GetGlobalColor(_T ( "URED" )), 2);
13034
13035 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13036 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13037 dc.SetBrush(*ppBrush);
13038
13039 if (lpp1 > 0) {
13040 dc.SetPen(ppPeng);
13041 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13042 }
13043
13044 if (lpp2 > 0) {
13045 dc.SetPen(ppPeng);
13046 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13047 }
13048
13049 if (lpp1 < 0) {
13050 dc.SetPen(ppPenr);
13051 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13052 }
13053
13054 if (lpp2 < 0) {
13055 dc.SetPen(ppPenr);
13056 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13057 }
13058 }
13059}
13060
13061double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13062 double lpp = 0.;
13063 wxPoint r1;
13064 wxPoint lAnchorPoint;
13065 double d1 = 0.0;
13066 double dabs;
13067 double tlat1, tlon1;
13068
13069 if (pAnchorWatchPoint) {
13070 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13071 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13072 dabs = fabs(d1 / 1852.);
13073 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13074 &tlat1, &tlon1);
13075 GetCanvasPointPix(tlat1, tlon1, &r1);
13076 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13077 &lAnchorPoint);
13078 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13079 pow((double)(lAnchorPoint.y - r1.y), 2));
13080
13081 // This is an entry watch
13082 if (d1 < 0) lpp = -lpp;
13083 }
13084 return lpp;
13085}
13086
13087//------------------------------------------------------------------------------------------
13088// Tides Support
13089//------------------------------------------------------------------------------------------
13090void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13091 if (!ptcmgr) return;
13092
13093 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13094
13095 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13096 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13097 double lon = pIDX->IDX_lon;
13098 double lat = pIDX->IDX_lat;
13099
13100 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13101 if ((type == 't') || (type == 'T')) {
13102 if (BBox.Contains(lat, lon)) {
13103 // Manage the point selection list
13104 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13105 }
13106 }
13107 }
13108}
13109
13110void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13111 if (!ptcmgr) return;
13112
13113 wxDateTime this_now = gTimeSource;
13114 bool cur_time = !gTimeSource.IsValid();
13115 if (cur_time) this_now = wxDateTime::Now();
13116 time_t t_this_now = this_now.GetTicks();
13117
13118 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
13119 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
13120 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13121 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )), 1,
13122 wxPENSTYLE_SOLID);
13123 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13124 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )), 1,
13125 wxPENSTYLE_SOLID);
13126
13127 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13128 GetGlobalColor(_T ( "GREEN1" )), wxBRUSHSTYLE_SOLID);
13129 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13130 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )),
13131 wxBRUSHSTYLE_SOLID);
13132 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13133 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )),
13134 wxBRUSHSTYLE_SOLID);
13135
13136 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13137 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13138 int font_size = wxMax(10, dFont->GetPointSize());
13139 font_size /= g_Platform->GetDisplayDIPMult(this);
13140 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13141 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13142 false, dFont->GetFaceName());
13143
13144 dc.SetPen(*pblack_pen);
13145 dc.SetBrush(*pgreen_brush);
13146
13147 wxBitmap bm;
13148 switch (m_cs) {
13149 case GLOBAL_COLOR_SCHEME_DAY:
13150 bm = m_bmTideDay;
13151 break;
13152 case GLOBAL_COLOR_SCHEME_DUSK:
13153 bm = m_bmTideDusk;
13154 break;
13155 case GLOBAL_COLOR_SCHEME_NIGHT:
13156 bm = m_bmTideNight;
13157 break;
13158 default:
13159 bm = m_bmTideDay;
13160 break;
13161 }
13162
13163 int bmw = bm.GetWidth();
13164 int bmh = bm.GetHeight();
13165
13166 float scale_factor = 1.0;
13167
13168 // Set the onscreen size of the symbol
13169 // Compensate for various display resolutions
13170 float icon_pixelRefDim = 45;
13171
13172 // Tidal report graphic is scaled by the text size of the label in use
13173 wxScreenDC sdc;
13174 int height;
13175 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13176 height *= g_Platform->GetDisplayDIPMult(this);
13177 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13178
13179 scale_factor *= pix_factor;
13180
13181 float user_scale_factor = g_ChartScaleFactorExp;
13182 if (g_ChartScaleFactorExp > 1.0)
13183 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13184 1.2; // soften the scale factor a bit
13185
13186 scale_factor *= user_scale_factor;
13187 scale_factor *= GetContentScaleFactor();
13188
13189 {
13190 double marge = 0.05;
13191 std::vector<LLBBox> drawn_boxes;
13192 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13193 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13194
13195 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13196 if ((type == 't') || (type == 'T')) // only Tides
13197 {
13198 double lon = pIDX->IDX_lon;
13199 double lat = pIDX->IDX_lat;
13200
13201 if (BBox.ContainsMarge(lat, lon, marge)) {
13202 // Avoid drawing detailed graphic for duplicate tide stations
13203 if (GetVP().chart_scale < 500000) {
13204 bool bdrawn = false;
13205 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13206 if (drawn_boxes[i].Contains(lat, lon)) {
13207 bdrawn = true;
13208 break;
13209 }
13210 }
13211 if (bdrawn) continue; // the station loop
13212
13213 LLBBox this_box;
13214 this_box.Set(lat, lon, lat, lon);
13215 this_box.EnLarge(.005);
13216 drawn_boxes.push_back(this_box);
13217 }
13218
13219 wxPoint r;
13220 GetCanvasPointPix(lat, lon, &r);
13221 // draw standard icons
13222 if (GetVP().chart_scale > 500000) {
13223 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13224 }
13225 // draw "extended" icons
13226 else {
13227 dc.SetFont(*plabelFont);
13228 {
13229 {
13230 float val, nowlev;
13231 float ltleve = 0.;
13232 float htleve = 0.;
13233 time_t tctime;
13234 time_t lttime = 0;
13235 time_t httime = 0;
13236 bool wt;
13237 // define if flood or ebb in the last ten minutes and verify if
13238 // data are useable
13239 if (ptcmgr->GetTideFlowSens(
13240 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13241 pIDX->IDX_rec_num, nowlev, val, wt)) {
13242 // search forward the first HW or LW near "now" ( starting at
13243 // "now" - ten minutes )
13244 ptcmgr->GetHightOrLowTide(
13245 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13246 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13247 wt, pIDX->IDX_rec_num, val, tctime);
13248 if (wt) {
13249 httime = tctime;
13250 htleve = val;
13251 } else {
13252 lttime = tctime;
13253 ltleve = val;
13254 }
13255 wt = !wt;
13256
13257 // then search opposite tide near "now"
13258 if (tctime > t_this_now) // search backward
13259 ptcmgr->GetHightOrLowTide(
13260 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13261 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13262 pIDX->IDX_rec_num, val, tctime);
13263 else
13264 // or search forward
13265 ptcmgr->GetHightOrLowTide(
13266 t_this_now, FORWARD_TEN_MINUTES_STEP,
13267 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13268 val, tctime);
13269 if (wt) {
13270 httime = tctime;
13271 htleve = val;
13272 } else {
13273 lttime = tctime;
13274 ltleve = val;
13275 }
13276
13277 // draw the tide rectangle:
13278
13279 // tide icon rectangle has default pre-scaled width = 12 ,
13280 // height = 45
13281 int width = (int)(12 * scale_factor + 0.5);
13282 int height = (int)(45 * scale_factor + 0.5);
13283 int linew = wxMax(1, (int)(scale_factor));
13284 int xDraw = r.x - (width / 2);
13285 int yDraw = r.y - (height / 2);
13286
13287 // process tide state ( %height and flow sens )
13288 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13289 int hs = (httime > lttime) ? -4 : 4;
13290 hs *= (int)(scale_factor + 0.5);
13291 if (ts > 0.995 || ts < 0.005) hs = 0;
13292 int ht_y = (int)(height * ts);
13293
13294 // draw yellow tide rectangle outlined in black
13295 pblack_pen->SetWidth(linew);
13296 dc.SetPen(*pblack_pen);
13297 dc.SetBrush(*pyelo_brush);
13298 dc.DrawRectangle(xDraw, yDraw, width, height);
13299
13300 // draw blue rectangle as water height, smaller in width than
13301 // yellow rectangle
13302 dc.SetPen(*pblue_pen);
13303 dc.SetBrush(*pblue_brush);
13304 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13305 (width - (4 * linew)), height - ht_y);
13306
13307 // draw sens arrows (ensure they are not "under-drawn" by top
13308 // line of blue rectangle )
13309 int hl;
13310 wxPoint arrow[3];
13311 arrow[0].x = xDraw + 2 * linew;
13312 arrow[1].x = xDraw + width / 2;
13313 arrow[2].x = xDraw + width - 2 * linew;
13314 pyelo_pen->SetWidth(linew);
13315 pblue_pen->SetWidth(linew);
13316 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13317 {
13318 hl = (int)(height * 0.25) + yDraw;
13319 arrow[0].y = hl;
13320 arrow[1].y = hl + hs;
13321 arrow[2].y = hl;
13322 if (ts < 0.15)
13323 dc.SetPen(*pyelo_pen);
13324 else
13325 dc.SetPen(*pblue_pen);
13326 dc.DrawLines(3, arrow);
13327 }
13328 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13329 {
13330 hl = (int)(height * 0.5) + yDraw;
13331 arrow[0].y = hl;
13332 arrow[1].y = hl + hs;
13333 arrow[2].y = hl;
13334 if (ts < 0.40)
13335 dc.SetPen(*pyelo_pen);
13336 else
13337 dc.SetPen(*pblue_pen);
13338 dc.DrawLines(3, arrow);
13339 }
13340 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13341 {
13342 hl = (int)(height * 0.75) + yDraw;
13343 arrow[0].y = hl;
13344 arrow[1].y = hl + hs;
13345 arrow[2].y = hl;
13346 if (ts < 0.65)
13347 dc.SetPen(*pyelo_pen);
13348 else
13349 dc.SetPen(*pblue_pen);
13350 dc.DrawLines(3, arrow);
13351 }
13352 // draw tide level text
13353 wxString s;
13354 s.Printf("%3.1f", nowlev);
13355 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13356 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13357 int wx1;
13358 dc.GetTextExtent(s, &wx1, NULL);
13359 wx1 *= g_Platform->GetDisplayDIPMult(this);
13360 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13361 }
13362 }
13363 }
13364 }
13365 }
13366 }
13367 }
13368 }
13369}
13370
13371//------------------------------------------------------------------------------------------
13372// Currents Support
13373//------------------------------------------------------------------------------------------
13374
13375void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13376 if (!ptcmgr) return;
13377
13378 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13379
13380 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13381 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13382 double lon = pIDX->IDX_lon;
13383 double lat = pIDX->IDX_lat;
13384
13385 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13386 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13387 if ((BBox.Contains(lat, lon))) {
13388 // Manage the point selection list
13389 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13390 }
13391 }
13392 }
13393}
13394
13395void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13396 if (!ptcmgr) return;
13397
13398 float tcvalue, dir;
13399 bool bnew_val;
13400 char sbuf[20];
13401 wxFont *pTCFont;
13402 double lon_last = 0.;
13403 double lat_last = 0.;
13404 // arrow size for Raz Blanchard : 12 knots north
13405 double marge = 0.2;
13406 bool cur_time = !gTimeSource.IsValid();
13407
13408 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13409 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13410
13411 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
13412 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
13413 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13414 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )), 1,
13415 wxPENSTYLE_SOLID);
13416 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13417 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )),
13418 wxBRUSHSTYLE_SOLID);
13419 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13420 GetGlobalColor(_T ( "UIBDR" )), wxBRUSHSTYLE_SOLID);
13421 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13422 GetGlobalColor(_T ( "UINFD" )), wxBRUSHSTYLE_SOLID);
13423
13424 double skew_angle = GetVPRotation();
13425
13426 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13427 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13428 int font_size = wxMax(10, dFont->GetPointSize());
13429 font_size /= g_Platform->GetDisplayDIPMult(this);
13430 pTCFont = FontMgr::Get().FindOrCreateFont(
13431 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13432 false, dFont->GetFaceName());
13433
13434 float scale_factor = 1.0;
13435
13436 // Set the onscreen size of the symbol
13437 // Current report graphic is scaled by the text size of the label in use
13438 wxScreenDC sdc;
13439 int height;
13440 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13441 height *= g_Platform->GetDisplayDIPMult(this);
13442 float nominal_icon_size_pixels = 15;
13443 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13444
13445 scale_factor *= pix_factor;
13446
13447 float user_scale_factor = g_ChartScaleFactorExp;
13448 if (g_ChartScaleFactorExp > 1.0)
13449 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13450 1.2; // soften the scale factor a bit
13451
13452 scale_factor *= user_scale_factor;
13453
13454 scale_factor *= GetContentScaleFactor();
13455
13456 {
13457 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13458 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13459 double lon = pIDX->IDX_lon;
13460 double lat = pIDX->IDX_lat;
13461
13462 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13463 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13464 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13465 wxPoint r;
13466 GetCanvasPointPix(lat, lon, &r);
13467
13468 wxPoint d[4]; // points of a diamond at the current station location
13469 int dd = (int)(5.0 * scale_factor + 0.5);
13470 d[0].x = r.x;
13471 d[0].y = r.y + dd;
13472 d[1].x = r.x + dd;
13473 d[1].y = r.y;
13474 d[2].x = r.x;
13475 d[2].y = r.y - dd;
13476 d[3].x = r.x - dd;
13477 d[3].y = r.y;
13478
13479 if (1) {
13480 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13481 dc.SetPen(*pblack_pen);
13482 dc.SetBrush(*porange_brush);
13483 dc.DrawPolygon(4, d);
13484
13485 if (type == 'C') {
13486 dc.SetBrush(*pblack_brush);
13487 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13488 }
13489
13490 if (GetVP().chart_scale < 1000000) {
13491 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13492 continue;
13493 } else
13494 continue;
13495
13496 if (1 /*type == 'c'*/) {
13497 {
13498 // Get the display pixel location of the current station
13499 int pixxc, pixyc;
13500 pixxc = r.x;
13501 pixyc = r.y;
13502
13503 // Adjust drawing size using logarithmic scale. tcvalue is
13504 // current in knots
13505 double a1 = fabs(tcvalue) * 10.;
13506 // Current values <= 0.1 knot will have no arrow
13507 a1 = wxMax(1.0, a1);
13508 double a2 = log10(a1);
13509
13510 float cscale = scale_factor * a2 * 0.3;
13511
13512 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13513 dc.SetPen(*porange_pen);
13514 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13515 cscale);
13516 // Draw text, if enabled
13517
13518 if (bDrawCurrentValues) {
13519 dc.SetFont(*pTCFont);
13520 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13521 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13522 }
13523 }
13524 } // scale
13525 }
13526 /* This is useful for debugging the TC database
13527 else
13528 {
13529 dc.SetPen ( *porange_pen );
13530 dc.SetBrush ( *pgray_brush );
13531 dc.DrawPolygon ( 4, d );
13532 }
13533 */
13534 }
13535 lon_last = lon;
13536 lat_last = lat;
13537 }
13538 }
13539 }
13540}
13541
13542void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13543 ShowSingleTideDialog(x, y, pvIDX);
13544}
13545
13546void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13547 if (!pvIDX) return; // Validate input
13548
13549 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13550
13551 // Check if a tide dialog is already open and visible
13552 if (pCwin && pCwin->IsShown()) {
13553 // Same tide station: bring existing dialog to front (preserves user
13554 // context)
13555 if (pCwin->GetCurrentIDX() == pNewIDX) {
13556 pCwin->Raise();
13557 pCwin->SetFocus();
13558
13559 // Provide subtle visual feedback that dialog is already open
13560 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13561 return;
13562 }
13563
13564 // Different tide station: close current dialog before opening new one
13565 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13566 }
13567
13568 if (pCwin) {
13569 // This shouldn't happen but ensures clean state
13570 pCwin->Destroy();
13571 pCwin = NULL;
13572 }
13573
13574 // Create and display new tide dialog
13575 pCwin = new TCWin(this, x, y, pvIDX);
13576
13577 // Ensure the dialog is properly shown and focused
13578 if (pCwin) {
13579 pCwin->Show();
13580 pCwin->Raise();
13581 pCwin->SetFocus();
13582 }
13583}
13584
13585bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13586
13588 if (pCwin) {
13589 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13590 }
13591}
13592
13593#define NUM_CURRENT_ARROW_POINTS 9
13594static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13595 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13596 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13597 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13598
13599void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13600 double scale) {
13601 if (scale > 1e-2) {
13602 float sin_rot = sin(rot_angle * PI / 180.);
13603 float cos_rot = cos(rot_angle * PI / 180.);
13604
13605 // Move to the first point
13606
13607 float xt = CurrentArrowArray[0].x;
13608 float yt = CurrentArrowArray[0].y;
13609
13610 float xp = (xt * cos_rot) - (yt * sin_rot);
13611 float yp = (xt * sin_rot) + (yt * cos_rot);
13612 int x1 = (int)(xp * scale);
13613 int y1 = (int)(yp * scale);
13614
13615 // Walk thru the point list
13616 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13617 xt = CurrentArrowArray[ip].x;
13618 yt = CurrentArrowArray[ip].y;
13619
13620 float xp = (xt * cos_rot) - (yt * sin_rot);
13621 float yp = (xt * sin_rot) + (yt * cos_rot);
13622 int x2 = (int)(xp * scale);
13623 int y2 = (int)(yp * scale);
13624
13625 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13626
13627 x1 = x2;
13628 y1 = y2;
13629 }
13630 }
13631}
13632
13633wxString ChartCanvas::FindValidUploadPort() {
13634 wxString port;
13635 // Try to use the saved persistent upload port first
13636 if (!g_uploadConnection.IsEmpty() &&
13637 g_uploadConnection.StartsWith("Serial")) {
13638 port = g_uploadConnection;
13639 }
13640
13641 else {
13642 // If there is no persistent upload port recorded (yet)
13643 // then use the first available serial connection which has output defined.
13644 for (auto *cp : TheConnectionParams()) {
13645 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13646 port << "Serial:" << cp->Port;
13647 }
13648 }
13649 return port;
13650}
13651
13652void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13653 if (!win) return;
13654
13655 if (NULL == g_pais_query_dialog_active) {
13656 int pos_x = g_ais_query_dialog_x;
13657 int pos_y = g_ais_query_dialog_y;
13658
13659 if (g_pais_query_dialog_active) {
13660 g_pais_query_dialog_active->Destroy();
13661 g_pais_query_dialog_active = new AISTargetQueryDialog();
13662 } else {
13663 g_pais_query_dialog_active = new AISTargetQueryDialog();
13664 }
13665
13666 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13667 wxPoint(pos_x, pos_y));
13668
13669 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13670 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13671 g_pais_query_dialog_active->SetMMSI(mmsi);
13672 g_pais_query_dialog_active->UpdateText();
13673 wxSize sz = g_pais_query_dialog_active->GetSize();
13674
13675 bool b_reset_pos = false;
13676#ifdef __WXMSW__
13677 // Support MultiMonitor setups which an allow negative window positions.
13678 // If the requested window title bar does not intersect any installed
13679 // monitor, then default to simple primary monitor positioning.
13680 RECT frame_title_rect;
13681 frame_title_rect.left = pos_x;
13682 frame_title_rect.top = pos_y;
13683 frame_title_rect.right = pos_x + sz.x;
13684 frame_title_rect.bottom = pos_y + 30;
13685
13686 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13687 b_reset_pos = true;
13688#else
13689
13690 // Make sure drag bar (title bar) of window intersects wxClient Area of
13691 // screen, with a little slop...
13692 wxRect window_title_rect; // conservative estimate
13693 window_title_rect.x = pos_x;
13694 window_title_rect.y = pos_y;
13695 window_title_rect.width = sz.x;
13696 window_title_rect.height = 30;
13697
13698 wxRect ClientRect = wxGetClientDisplayRect();
13699 ClientRect.Deflate(
13700 60, 60); // Prevent the new window from being too close to the edge
13701 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13702
13703#endif
13704
13705 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13706
13707 } else {
13708 g_pais_query_dialog_active->SetMMSI(mmsi);
13709 g_pais_query_dialog_active->UpdateText();
13710 }
13711
13712 g_pais_query_dialog_active->Show();
13713}
13714
13715void ChartCanvas::ToggleCanvasQuiltMode(void) {
13716 bool cur_mode = GetQuiltMode();
13717
13718 if (!GetQuiltMode())
13719 SetQuiltMode(true);
13720 else if (GetQuiltMode()) {
13721 SetQuiltMode(false);
13722 g_sticky_chart = GetQuiltReferenceChartIndex();
13723 }
13724
13725 if (cur_mode != GetQuiltMode()) {
13726 SetupCanvasQuiltMode();
13727 DoCanvasUpdate();
13728 InvalidateGL();
13729 Refresh();
13730 }
13731 // TODO What to do about this?
13732 // g_bQuiltEnable = GetQuiltMode();
13733
13734 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13735 if (ps52plib) ps52plib->GenerateStateHash();
13736
13737 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13738 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13739}
13740
13741void ChartCanvas::DoCanvasStackDelta(int direction) {
13742 if (!GetQuiltMode()) {
13743 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13744 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13745 if ((current_stack_index + direction) < 0) return;
13746
13747 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13748 int new_dbIndex =
13749 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13750
13751 if (IsChartQuiltableRef(new_dbIndex)) {
13752 ToggleCanvasQuiltMode();
13753 SelectQuiltRefdbChart(new_dbIndex);
13754 m_bpersistent_quilt = false;
13755 }
13756 } else {
13757 SelectChartFromStack(current_stack_index + direction);
13758 }
13759 } else {
13760 std::vector<int> piano_chart_index_array =
13761 GetQuiltExtendedStackdbIndexArray();
13762 int refdb = GetQuiltRefChartdbIndex();
13763
13764 // Find the ref chart in the stack
13765 int current_index = -1;
13766 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13767 if (refdb == piano_chart_index_array[i]) {
13768 current_index = i;
13769 break;
13770 }
13771 }
13772 if (current_index == -1) return;
13773
13774 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13775 int target_family = ctet.GetChartFamily();
13776
13777 int new_index = -1;
13778 int check_index = current_index + direction;
13779 bool found = false;
13780 int check_dbIndex = -1;
13781 int new_dbIndex = -1;
13782
13783 // When quilted. switch within the same chart family
13784 while (!found &&
13785 (unsigned int)check_index < piano_chart_index_array.size() &&
13786 (check_index >= 0)) {
13787 check_dbIndex = piano_chart_index_array[check_index];
13788 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13789 if (target_family == cte.GetChartFamily()) {
13790 found = true;
13791 new_index = check_index;
13792 new_dbIndex = check_dbIndex;
13793 break;
13794 }
13795
13796 check_index += direction;
13797 }
13798
13799 if (!found) return;
13800
13801 if (!IsChartQuiltableRef(new_dbIndex)) {
13802 ToggleCanvasQuiltMode();
13803 SelectdbChart(new_dbIndex);
13804 m_bpersistent_quilt = true;
13805 } else {
13806 SelectQuiltRefChart(new_index);
13807 }
13808 }
13809
13810 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13811 // (checkmarks etc)
13812 SetQuiltChartHiLiteIndex(-1);
13813
13814 ReloadVP();
13815}
13816
13817//--------------------------------------------------------------------------------------------------------
13818//
13819// Toolbar support
13820//
13821//--------------------------------------------------------------------------------------------------------
13822
13823void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13824 // Handle the per-canvas toolbar clicks here
13825
13826 switch (event.GetId()) {
13827 case ID_ZOOMIN: {
13828 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13829 break;
13830 }
13831
13832 case ID_ZOOMOUT: {
13833 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13834 break;
13835 }
13836
13837 case ID_STKUP:
13838 DoCanvasStackDelta(1);
13839 DoCanvasUpdate();
13840 break;
13841
13842 case ID_STKDN:
13843 DoCanvasStackDelta(-1);
13844 DoCanvasUpdate();
13845 break;
13846
13847 case ID_FOLLOW: {
13848 TogglebFollow();
13849 break;
13850 }
13851
13852 case ID_CURRENT: {
13853 ShowCurrents(!GetbShowCurrent());
13854 ReloadVP();
13855 Refresh(false);
13856 break;
13857 }
13858
13859 case ID_TIDE: {
13860 ShowTides(!GetbShowTide());
13861 ReloadVP();
13862 Refresh(false);
13863 break;
13864 }
13865
13866 case ID_ROUTE: {
13867 if (0 == m_routeState) {
13868 StartRoute();
13869 } else {
13870 FinishRoute();
13871 }
13872
13873#ifdef __ANDROID__
13874 androidSetRouteAnnunciator(m_routeState == 1);
13875#endif
13876 break;
13877 }
13878
13879 case ID_AIS: {
13880 SetAISCanvasDisplayStyle(-1);
13881 break;
13882 }
13883
13884 default:
13885 break;
13886 }
13887
13888 // And then let gFrame handle the rest....
13889 event.Skip();
13890}
13891
13892void ChartCanvas::SetShowAIS(bool show) {
13893 m_bShowAIS = show;
13894 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13895 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13896}
13897
13898void ChartCanvas::SetAttenAIS(bool show) {
13899 m_bShowAISScaled = show;
13900 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13901 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13902}
13903
13904void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
13905 // make some arrays to hold the dfferences between cycle steps
13906 // show all, scaled, hide all
13907 bool bShowAIS_Array[3] = {true, true, false};
13908 bool bShowScaled_Array[3] = {false, true, true};
13909 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
13910 _("Attenuate less critical AIS targets"),
13911 _("Hide AIS Targets")};
13912 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
13913 int ArraySize = 3;
13914 int AIS_Toolbar_Switch = 0;
13915 if (StyleIndx == -1) { // -1 means coming from toolbar button
13916 // find current state of switch
13917 for (int i = 1; i < ArraySize; i++) {
13918 if ((bShowAIS_Array[i] == m_bShowAIS) &&
13919 (bShowScaled_Array[i] == m_bShowAISScaled))
13920 AIS_Toolbar_Switch = i;
13921 }
13922 AIS_Toolbar_Switch++; // we did click so continu with next item
13923 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
13924 AIS_Toolbar_Switch++;
13925
13926 } else { // coming from menu bar.
13927 AIS_Toolbar_Switch = StyleIndx;
13928 }
13929 // make sure we are not above array
13930 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
13931
13932 int AIS_Toolbar_Switch_Next =
13933 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
13934 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
13935 AIS_Toolbar_Switch_Next++;
13936 if (AIS_Toolbar_Switch_Next >= ArraySize)
13937 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
13938
13939 // Set found values to global and member variables
13940 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
13941 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
13942}
13943
13944void ChartCanvas::TouchAISToolActive(void) {}
13945
13946void ChartCanvas::UpdateAISTBTool(void) {}
13947
13948//---------------------------------------------------------------------------------
13949//
13950// Compass/GPS status icon support
13951//
13952//---------------------------------------------------------------------------------
13953
13954void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
13955 // Look for change in overlap or positions
13956 bool b_update = false;
13957 int cc1_edge_comp = 2;
13958 wxRect rect = m_Compass->GetRect();
13959 wxSize parent_size = GetSize();
13960
13961 parent_size *= m_displayScale;
13962
13963 // check to see if it would overlap if it was in its home position (upper
13964 // right)
13965 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
13966 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
13967 wxRect compass_rect(compass_pt, rect.GetSize());
13968
13969 m_Compass->Move(compass_pt);
13970
13971 if (m_Compass && m_Compass->IsShown())
13972 m_Compass->UpdateStatus(b_force_new | b_update);
13973
13974 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
13975 scaler = wxMax(scaler, 1.0);
13976 wxPoint note_point = wxPoint(
13977 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
13978 m_notification_button->Move(note_point);
13979 m_notification_button->UpdateStatus();
13980
13981 if (b_force_new | b_update) Refresh();
13982}
13983
13984void ChartCanvas::SelectChartFromStack(int index, bool bDir,
13985 ChartTypeEnum New_Type,
13986 ChartFamilyEnum New_Family) {
13987 if (!GetpCurrentStack()) return;
13988 if (!ChartData) return;
13989
13990 if (index < GetpCurrentStack()->nEntry) {
13991 // Open the new chart
13992 ChartBase *pTentative_Chart;
13993 pTentative_Chart = ChartData->OpenStackChartConditional(
13994 GetpCurrentStack(), index, bDir, New_Type, New_Family);
13995
13996 if (pTentative_Chart) {
13997 if (m_singleChart) m_singleChart->Deactivate();
13998
13999 m_singleChart = pTentative_Chart;
14000 m_singleChart->Activate();
14001
14002 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14003 GetpCurrentStack(), m_singleChart->GetFullPath());
14004 }
14005
14006 // Setup the view
14007 double zLat, zLon;
14008 if (m_bFollow) {
14009 zLat = gLat;
14010 zLon = gLon;
14011 } else {
14012 zLat = m_vLat;
14013 zLon = m_vLon;
14014 }
14015
14016 double best_scale_ppm = GetBestVPScale(m_singleChart);
14017 double rotation = GetVPRotation();
14018 double oldskew = GetVPSkew();
14019 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14020
14021 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14022 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14023 if (fabs(newskew) > 0.0001) rotation = newskew;
14024 }
14025
14026 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14027
14028 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14029 }
14030
14031 // refresh Piano
14032 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14033 if (idx < 0) return;
14034
14035 std::vector<int> piano_active_chart_index_array;
14036 piano_active_chart_index_array.push_back(
14037 GetpCurrentStack()->GetCurrentEntrydbIndex());
14038 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14039}
14040
14041void ChartCanvas::SelectdbChart(int dbindex) {
14042 if (!GetpCurrentStack()) return;
14043 if (!ChartData) return;
14044
14045 if (dbindex >= 0) {
14046 // Open the new chart
14047 ChartBase *pTentative_Chart;
14048 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14049
14050 if (pTentative_Chart) {
14051 if (m_singleChart) m_singleChart->Deactivate();
14052
14053 m_singleChart = pTentative_Chart;
14054 m_singleChart->Activate();
14055
14056 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14057 GetpCurrentStack(), m_singleChart->GetFullPath());
14058 }
14059
14060 // Setup the view
14061 double zLat, zLon;
14062 if (m_bFollow) {
14063 zLat = gLat;
14064 zLon = gLon;
14065 } else {
14066 zLat = m_vLat;
14067 zLon = m_vLon;
14068 }
14069
14070 double best_scale_ppm = GetBestVPScale(m_singleChart);
14071
14072 if (m_singleChart)
14073 SetViewPoint(zLat, zLon, best_scale_ppm,
14074 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14075
14076 // SetChartUpdatePeriod( );
14077
14078 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14079 }
14080
14081 // TODO refresh_Piano();
14082}
14083
14084void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14085 double target_scale = GetVP().view_scale_ppm;
14086
14087 if (!GetQuiltMode()) {
14088 if (GetpCurrentStack()) {
14089 int stack_index = -1;
14090 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14091 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14092 if (check_dbIndex < 0) continue;
14093 const ChartTableEntry &cte =
14094 ChartData->GetChartTableEntry(check_dbIndex);
14095 if (type == cte.GetChartType()) {
14096 stack_index = i;
14097 break;
14098 } else if (family == cte.GetChartFamily()) {
14099 stack_index = i;
14100 break;
14101 }
14102 }
14103
14104 if (stack_index >= 0) {
14105 SelectChartFromStack(stack_index);
14106 }
14107 }
14108 } else {
14109 int sel_dbIndex = -1;
14110 std::vector<int> piano_chart_index_array =
14111 GetQuiltExtendedStackdbIndexArray();
14112 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14113 int check_dbIndex = piano_chart_index_array[i];
14114 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14115 if (type == cte.GetChartType()) {
14116 if (IsChartQuiltableRef(check_dbIndex)) {
14117 sel_dbIndex = check_dbIndex;
14118 break;
14119 }
14120 } else if (family == cte.GetChartFamily()) {
14121 if (IsChartQuiltableRef(check_dbIndex)) {
14122 sel_dbIndex = check_dbIndex;
14123 break;
14124 }
14125 }
14126 }
14127
14128 if (sel_dbIndex >= 0) {
14129 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14130 // Re-qualify the quilt reference chart selection
14131 AdjustQuiltRefChart();
14132 }
14133
14134 // Now reset the scale to the target...
14135 SetVPScale(target_scale);
14136 }
14137
14138 SetQuiltChartHiLiteIndex(-1);
14139
14140 ReloadVP();
14141}
14142
14143bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14144 return std::find(m_tile_yesshow_index_array.begin(),
14145 m_tile_yesshow_index_array.end(),
14146 index) != m_tile_yesshow_index_array.end();
14147}
14148
14149bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14150 return std::find(m_tile_noshow_index_array.begin(),
14151 m_tile_noshow_index_array.end(),
14152 index) != m_tile_noshow_index_array.end();
14153}
14154
14155void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14156 if (std::find(m_tile_noshow_index_array.begin(),
14157 m_tile_noshow_index_array.end(),
14158 index) == m_tile_noshow_index_array.end()) {
14159 m_tile_noshow_index_array.push_back(index);
14160 }
14161}
14162
14163//-------------------------------------------------------------------------------------------------------
14164//
14165// Piano support
14166//
14167//-------------------------------------------------------------------------------------------------------
14168
14169void ChartCanvas::HandlePianoClick(
14170 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14171 if (g_options && g_options->IsShown())
14172 return; // Piano might be invalid due to chartset updates.
14173 if (!m_pCurrentStack) return;
14174 if (!ChartData) return;
14175
14176 // stop movement or on slow computer we may get something like :
14177 // zoom out with the wheel (timer is set)
14178 // quickly click and display a chart, which may zoom in
14179 // but the delayed timer fires first and it zooms out again!
14180 StopMovement();
14181
14182 // When switching by piano key click, we may appoint the new target chart to
14183 // be any chart in the composite array.
14184 // As an improvement to UX, find the chart that is "closest" to the current
14185 // vp,
14186 // and select that chart. This will cause a jump to the centroid of that
14187 // chart
14188
14189 double distance = 25000; // RTW
14190 int closest_index = -1;
14191 for (int chart_index : selected_dbIndex_array) {
14192 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14193 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14194 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14195
14196 // measure distance as Manhattan style
14197 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14198 if (test_distance < distance) {
14199 distance = test_distance;
14200 closest_index = chart_index;
14201 }
14202 }
14203
14204 int selected_dbIndex = selected_dbIndex_array[0];
14205 if (closest_index >= 0) selected_dbIndex = closest_index;
14206
14207 if (!GetQuiltMode()) {
14208 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14209 if (IsChartQuiltableRef(selected_dbIndex)) {
14210 ToggleCanvasQuiltMode();
14211 SelectQuiltRefdbChart(selected_dbIndex);
14212 m_bpersistent_quilt = false;
14213 } else {
14214 SelectChartFromStack(selected_index);
14215 }
14216 } else {
14217 SelectChartFromStack(selected_index);
14218 g_sticky_chart = selected_dbIndex;
14219 }
14220
14221 if (m_singleChart)
14222 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14223 } else {
14224 // Handle MBTiles overlays first
14225 // Left click simply toggles the noshow array index entry
14226 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14227 bool bfound = false;
14228 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14229 if (m_tile_noshow_index_array[i] ==
14230 selected_dbIndex) { // chart is in the noshow list
14231 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14232 i); // erase it
14233 bfound = true;
14234 break;
14235 }
14236 }
14237 if (!bfound) {
14238 m_tile_noshow_index_array.push_back(selected_dbIndex);
14239 }
14240
14241 // If not already present, add this tileset to the "yes_show" array.
14242 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14243 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14244 }
14245
14246 else {
14247 if (IsChartQuiltableRef(selected_dbIndex)) {
14248 // if( ChartData ) ChartData->PurgeCache();
14249
14250 // If the chart is a vector chart, and of very large scale,
14251 // then we had better set the new scale directly to avoid excessive
14252 // underzoom on, eg, Inland ENCs
14253 bool set_scale = false;
14254 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14255 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14256 set_scale = true;
14257 }
14258 }
14259
14260 if (!set_scale) {
14261 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14262 } else {
14263 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14264
14265 // Adjust scale so that the selected chart is underzoomed/overzoomed
14266 // by a controlled amount
14267 ChartBase *pc =
14268 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14269 if (pc) {
14270 double proposed_scale_onscreen =
14272
14273 if (g_bPreserveScaleOnX) {
14274 proposed_scale_onscreen =
14275 wxMin(proposed_scale_onscreen,
14276 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14277 GetCanvasWidth()));
14278 } else {
14279 proposed_scale_onscreen =
14280 wxMin(proposed_scale_onscreen,
14281 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14282 GetCanvasWidth()));
14283
14284 proposed_scale_onscreen =
14285 wxMax(proposed_scale_onscreen,
14286 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14288 }
14289
14290 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14291 }
14292 }
14293 } else {
14294 ToggleCanvasQuiltMode();
14295 SelectdbChart(selected_dbIndex);
14296 m_bpersistent_quilt = true;
14297 }
14298 }
14299 }
14300
14301 SetQuiltChartHiLiteIndex(-1);
14302 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14303 // (checkmarks etc)
14304 HideChartInfoWindow();
14305 DoCanvasUpdate();
14306 ReloadVP(); // Pick up the new selections
14307}
14308
14309void ChartCanvas::HandlePianoRClick(
14310 int x, int y, int selected_index,
14311 const std::vector<int> &selected_dbIndex_array) {
14312 if (g_options && g_options->IsShown())
14313 return; // Piano might be invalid due to chartset updates.
14314 if (!GetpCurrentStack()) return;
14315
14316 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14317 UpdateCanvasControlBar();
14318
14319 SetQuiltChartHiLiteIndex(-1);
14320}
14321
14322void ChartCanvas::HandlePianoRollover(
14323 int selected_index, const std::vector<int> &selected_dbIndex_array,
14324 int n_charts, int scale) {
14325 if (g_options && g_options->IsShown())
14326 return; // Piano might be invalid due to chartset updates.
14327 if (!GetpCurrentStack()) return;
14328 if (!ChartData) return;
14329
14330 if (ChartData->IsBusy()) return;
14331
14332 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14333
14334 if (!GetQuiltMode()) {
14335 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14336 } else {
14337 // Select the correct vector
14338 std::vector<int> piano_chart_index_array;
14339 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14340 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14341 if ((GetpCurrentStack()->nEntry > 1) ||
14342 (piano_chart_index_array.size() >= 1)) {
14343 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14344
14345 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14346 ReloadVP(false); // no VP adjustment allowed
14347 } else if (GetpCurrentStack()->nEntry == 1) {
14348 const ChartTableEntry &cte =
14349 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14350 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14351 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14352 ReloadVP(false);
14353 } else if ((-1 == selected_index) &&
14354 (0 == selected_dbIndex_array.size())) {
14355 ShowChartInfoWindow(key_location.x, -1);
14356 }
14357 }
14358 } else {
14359 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14360
14361 if ((GetpCurrentStack()->nEntry > 1) ||
14362 (piano_chart_index_array.size() >= 1)) {
14363 if (n_charts > 1)
14364 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14365 selected_dbIndex_array);
14366 else if (n_charts == 1)
14367 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14368
14369 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14370 ReloadVP(false); // no VP adjustment allowed
14371 }
14372 }
14373 }
14374}
14375
14376void ChartCanvas::ClearPianoRollover() {
14377 ClearQuiltChartHiLiteIndexArray();
14378 ShowChartInfoWindow(0, -1);
14379 std::vector<int> vec;
14380 ShowCompositeInfoWindow(0, 0, 0, vec);
14381 ReloadVP(false);
14382}
14383
14384void ChartCanvas::UpdateCanvasControlBar(void) {
14385 if (m_pianoFrozen) return;
14386
14387 if (!GetpCurrentStack()) return;
14388 if (!ChartData) return;
14389 if (!g_bShowChartBar) return;
14390
14391 int sel_type = -1;
14392 int sel_family = -1;
14393
14394 std::vector<int> piano_chart_index_array;
14395 std::vector<int> empty_piano_chart_index_array;
14396
14397 wxString old_hash = m_Piano->GetStoredHash();
14398
14399 if (GetQuiltMode()) {
14400 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14401 GetQuiltFullScreendbIndexArray());
14402
14403 std::vector<int> piano_active_chart_index_array =
14404 GetQuiltCandidatedbIndexArray();
14405 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14406
14407 std::vector<int> piano_eclipsed_chart_index_array =
14408 GetQuiltEclipsedStackdbIndexArray();
14409 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14410
14411 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14412 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14413
14414 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14415 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14416 } else {
14417 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14418 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14419 // TODO refresh_Piano();
14420
14421 if (m_singleChart) {
14422 sel_type = m_singleChart->GetChartType();
14423 sel_family = m_singleChart->GetChartFamily();
14424 }
14425 }
14426
14427 // Set up the TMerc and Skew arrays
14428 std::vector<int> piano_skew_chart_index_array;
14429 std::vector<int> piano_tmerc_chart_index_array;
14430 std::vector<int> piano_poly_chart_index_array;
14431
14432 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14433 const ChartTableEntry &ctei =
14434 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14435 double skew_norm = ctei.GetChartSkew();
14436 if (skew_norm > 180.) skew_norm -= 360.;
14437
14438 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14439 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14440
14441 // Polyconic skewed charts should show as skewed
14442 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14443 if (fabs(skew_norm) > 1.)
14444 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14445 else
14446 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14447 } else if (fabs(skew_norm) > 1.)
14448 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14449 }
14450 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14451 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14452 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14453
14454 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14455 if (new_hash != old_hash) {
14456 m_Piano->FormatKeys();
14457 HideChartInfoWindow();
14458 m_Piano->ResetRollover();
14459 SetQuiltChartHiLiteIndex(-1);
14460 m_brepaint_piano = true;
14461 }
14462
14463 // Create a bitmask int that describes what Family/Type of charts are shown in
14464 // the bar, and notify the platform.
14465 int mask = 0;
14466 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14467 const ChartTableEntry &ctei =
14468 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14469 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14470 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14471 if (e == CHART_FAMILY_RASTER) mask |= 1;
14472 if (e == CHART_FAMILY_VECTOR) {
14473 if (t == CHART_TYPE_CM93COMP)
14474 mask |= 4;
14475 else
14476 mask |= 2;
14477 }
14478 }
14479
14480 wxString s_indicated;
14481 if (sel_type == CHART_TYPE_CM93COMP)
14482 s_indicated = "cm93";
14483 else {
14484 if (sel_family == CHART_FAMILY_RASTER)
14485 s_indicated = "raster";
14486 else if (sel_family == CHART_FAMILY_VECTOR)
14487 s_indicated = "vector";
14488 }
14489
14490 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14491}
14492
14493void ChartCanvas::FormatPianoKeys(void) { m_Piano->FormatKeys(); }
14494
14495void ChartCanvas::PianoPopupMenu(
14496 int x, int y, int selected_index,
14497 const std::vector<int> &selected_dbIndex_array) {
14498 if (!GetpCurrentStack()) return;
14499
14500 // No context menu if quilting is disabled
14501 if (!GetQuiltMode()) return;
14502
14503 m_piano_ctx_menu = new wxMenu();
14504
14505 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14506 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14507 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14508 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14509 } else {
14510 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14511 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14512 // wxEVT_COMMAND_MENU_SELECTED,
14513 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14514
14515 menu_selected_dbIndex = selected_dbIndex_array[0];
14516 menu_selected_index = selected_index;
14517
14518 // Search the no-show array
14519 bool b_is_in_noshow = false;
14520 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14521 if (m_quilt_noshow_index_array[i] ==
14522 menu_selected_dbIndex) // chart is in the noshow list
14523 {
14524 b_is_in_noshow = true;
14525 break;
14526 }
14527 }
14528
14529 if (b_is_in_noshow) {
14530 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14531 _("Show This Chart"));
14532 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14533 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14534 } else if (GetpCurrentStack()->nEntry > 1) {
14535 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14536 _("Hide This Chart"));
14537 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14538 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14539 }
14540 }
14541
14542 wxPoint pos = wxPoint(x, y - 30);
14543
14544 // Invoke the drop-down menu
14545 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14546 PopupMenu(m_piano_ctx_menu, pos);
14547
14548 delete m_piano_ctx_menu;
14549 m_piano_ctx_menu = NULL;
14550
14551 HideChartInfoWindow();
14552 m_Piano->ResetRollover();
14553
14554 SetQuiltChartHiLiteIndex(-1);
14555 ClearQuiltChartHiLiteIndexArray();
14556
14557 ReloadVP();
14558}
14559
14560void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14561 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14562 if (m_quilt_noshow_index_array[i] ==
14563 menu_selected_dbIndex) // chart is in the noshow list
14564 {
14565 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14566 break;
14567 }
14568 }
14569}
14570
14571void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14572 if (!GetpCurrentStack()) return;
14573 if (!ChartData) return;
14574
14575 RemoveChartFromQuilt(menu_selected_dbIndex);
14576
14577 // It could happen that the chart being disabled is the reference
14578 // chart....
14579 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14580 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14581
14582 int i = menu_selected_index + 1; // select next smaller scale chart
14583 bool b_success = false;
14584 while (i < GetpCurrentStack()->nEntry - 1) {
14585 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14586 if (type == ChartData->GetDBChartType(dbIndex)) {
14587 SelectQuiltRefChart(i);
14588 b_success = true;
14589 break;
14590 }
14591 i++;
14592 }
14593
14594 // If that did not work, try to select the next larger scale compatible
14595 // chart
14596 if (!b_success) {
14597 i = menu_selected_index - 1;
14598 while (i > 0) {
14599 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14600 if (type == ChartData->GetDBChartType(dbIndex)) {
14601 SelectQuiltRefChart(i);
14602 b_success = true;
14603 break;
14604 }
14605 i--;
14606 }
14607 }
14608 }
14609}
14610
14611void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14612 // Remove the item from the list (if it appears) to avoid multiple addition
14613 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14614 if (m_quilt_noshow_index_array[i] ==
14615 dbIndex) // chart is already in the noshow list
14616 {
14617 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14618 break;
14619 }
14620 }
14621
14622 m_quilt_noshow_index_array.push_back(dbIndex);
14623}
14624
14625bool ChartCanvas::UpdateS52State() {
14626 bool retval = false;
14627 // printf(" update %d\n", IsPrimaryCanvas());
14628
14629 if (ps52plib) {
14630 ps52plib->SetShowS57Text(m_encShowText);
14631 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14632 ps52plib->m_bShowSoundg = m_encShowDepth;
14633 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14634 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14635
14636 // Lights
14637 if (!m_encShowLights) // On, going off
14638 ps52plib->AddObjNoshow("LIGHTS");
14639 else // Off, going on
14640 ps52plib->RemoveObjNoshow("LIGHTS");
14641 ps52plib->SetLightsOff(!m_encShowLights);
14642 ps52plib->m_bExtendLightSectors = true;
14643
14644 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14645 ps52plib->SetAnchorOn(m_encShowAnchor);
14646 ps52plib->SetQualityOfData(m_encShowDataQual);
14647 }
14648
14649 return retval;
14650}
14651
14652void ChartCanvas::SetShowENCDataQual(bool show) {
14653 m_encShowDataQual = show;
14654 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14655 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14656
14657 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14658}
14659
14660void ChartCanvas::SetShowENCText(bool show) {
14661 m_encShowText = show;
14662 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14663 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14664
14665 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14666}
14667
14668void ChartCanvas::SetENCDisplayCategory(int category) {
14669 m_encDisplayCategory = category;
14670 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14671}
14672
14673void ChartCanvas::SetShowENCDepth(bool show) {
14674 m_encShowDepth = show;
14675 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14676 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14677
14678 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14679}
14680
14681void ChartCanvas::SetShowENCLightDesc(bool show) {
14682 m_encShowLightDesc = show;
14683 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14684 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14685
14686 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14687}
14688
14689void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14690 m_encShowBuoyLabels = show;
14691 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14692}
14693
14694void ChartCanvas::SetShowENCLights(bool show) {
14695 m_encShowLights = show;
14696 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14697 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14698
14699 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14700}
14701
14702void ChartCanvas::SetShowENCAnchor(bool show) {
14703 m_encShowAnchor = show;
14704 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14705 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14706
14707 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14708}
14709
14710wxRect ChartCanvas::GetMUIBarRect() {
14711 wxRect rv;
14712 if (m_muiBar) {
14713 rv = m_muiBar->GetRect();
14714 }
14715
14716 return rv;
14717}
14718
14719void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14720 if (!GetAlertString().IsEmpty()) {
14721 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14722 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14723
14724 dc.SetFont(*pfont);
14725 dc.SetPen(*wxTRANSPARENT_PEN);
14726
14727 dc.SetBrush(wxColour(243, 229, 47));
14728 int w, h;
14729 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14730 h += 2;
14731 // int yp = vp.pix_height - 20 - h;
14732
14733 wxRect sbr = GetScaleBarRect();
14734 int xp = sbr.x + sbr.width + 10;
14735 int yp = (sbr.y + sbr.height) - h;
14736
14737 int wdraw = w + 10;
14738 dc.DrawRectangle(xp, yp, wdraw, h);
14739 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14740 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14741 }
14742}
14743
14744//--------------------------------------------------------------------------------------------------------
14745// Screen Brightness Control Support Routines
14746//
14747//--------------------------------------------------------------------------------------------------------
14748
14749#ifdef __UNIX__
14750#define BRIGHT_XCALIB
14751#define __OPCPN_USEICC__
14752#endif
14753
14754#ifdef __OPCPN_USEICC__
14755int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14756 double co_green, double co_blue);
14757
14758wxString temp_file_name;
14759#endif
14760
14761#if 0
14762class ocpnCurtain: public wxDialog
14763{
14764 DECLARE_CLASS( ocpnCurtain )
14765 DECLARE_EVENT_TABLE()
14766
14767public:
14768 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14769 ~ocpnCurtain( );
14770 bool ProcessEvent(wxEvent& event);
14771
14772};
14773
14774IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14775
14776BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14777END_EVENT_TABLE()
14778
14779ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14780{
14781 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14782}
14783
14784ocpnCurtain::~ocpnCurtain()
14785{
14786}
14787
14788bool ocpnCurtain::ProcessEvent(wxEvent& event)
14789{
14790 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14791 return GetParent()->GetEventHandler()->ProcessEvent(event);
14792}
14793#endif
14794
14795#ifdef _WIN32
14796#include <windows.h>
14797
14798HMODULE hGDI32DLL;
14799typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14800typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14801SetDeviceGammaRamp_ptr_type
14802 g_pSetDeviceGammaRamp; // the API entry points in the dll
14803GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14804
14805WORD *g_pSavedGammaMap;
14806
14807#endif
14808
14809int InitScreenBrightness(void) {
14810#ifdef _WIN32
14811#ifdef ocpnUSE_GL
14812 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14813 HDC hDC;
14814 BOOL bbr;
14815
14816 if (NULL == hGDI32DLL) {
14817 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14818
14819 if (NULL != hGDI32DLL) {
14820 // Get the entry points of the required functions
14821 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14822 hGDI32DLL, "SetDeviceGammaRamp");
14823 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14824 hGDI32DLL, "GetDeviceGammaRamp");
14825
14826 // If the functions are not found, unload the DLL and return false
14827 if ((NULL == g_pSetDeviceGammaRamp) ||
14828 (NULL == g_pGetDeviceGammaRamp)) {
14829 FreeLibrary(hGDI32DLL);
14830 hGDI32DLL = NULL;
14831 return 0;
14832 }
14833 }
14834 }
14835
14836 // Interface is ready, so....
14837 // Get some storage
14838 if (!g_pSavedGammaMap) {
14839 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14840
14841 hDC = GetDC(NULL); // Get the full screen DC
14842 bbr = g_pGetDeviceGammaRamp(
14843 hDC, g_pSavedGammaMap); // Get the existing ramp table
14844 ReleaseDC(NULL, hDC); // Release the DC
14845 }
14846
14847 // On Windows hosts, try to adjust the registry to allow full range
14848 // setting of Gamma table This is an undocumented Windows hack.....
14849 wxRegKey *pRegKey = new wxRegKey(
14850 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
14851 "NT\\CurrentVersion\\ICM");
14852 if (!pRegKey->Exists()) pRegKey->Create();
14853 pRegKey->SetValue("GdiIcmGammaRange", 256);
14854
14855 g_brightness_init = true;
14856 return 1;
14857 }
14858#endif
14859
14860 {
14861 if (NULL == g_pcurtain) {
14862 if (gFrame->CanSetTransparent()) {
14863 // Build the curtain window
14864 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, "",
14865 wxPoint(0, 0), ::wxGetDisplaySize(),
14866 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14867 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14868
14869 // g_pcurtain = new ocpnCurtain(gFrame,
14870 // wxPoint(0,0),::wxGetDisplaySize(),
14871 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14872 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14873
14874 g_pcurtain->Hide();
14875
14876 HWND hWnd = GetHwndOf(g_pcurtain);
14877 SetWindowLong(hWnd, GWL_EXSTYLE,
14878 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14879 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14880 g_pcurtain->SetTransparent(0);
14881
14882 g_pcurtain->Maximize();
14883 g_pcurtain->Show();
14884
14885 // All of this is obtuse, but necessary for Windows...
14886 g_pcurtain->Enable();
14887 g_pcurtain->Disable();
14888
14889 gFrame->Disable();
14890 gFrame->Enable();
14891 // SetFocus();
14892 }
14893 }
14894 g_brightness_init = true;
14895
14896 return 1;
14897 }
14898#else
14899 // Look for "xcalib" application
14900 wxString cmd(_T ( "xcalib -version" ));
14901
14902 wxArrayString output;
14903 long r = wxExecute(cmd, output);
14904 if (0 != r)
14905 wxLogMessage(
14906 " External application \"xcalib\" not found. Screen brightness "
14907 "not changed.");
14908
14909 g_brightness_init = true;
14910 return 0;
14911#endif
14912}
14913
14914int RestoreScreenBrightness(void) {
14915#ifdef _WIN32
14916
14917 if (g_pSavedGammaMap) {
14918 HDC hDC = GetDC(NULL); // Get the full screen DC
14919 g_pSetDeviceGammaRamp(hDC,
14920 g_pSavedGammaMap); // Restore the saved ramp table
14921 ReleaseDC(NULL, hDC); // Release the DC
14922
14923 free(g_pSavedGammaMap);
14924 g_pSavedGammaMap = NULL;
14925 }
14926
14927 if (g_pcurtain) {
14928 g_pcurtain->Close();
14929 g_pcurtain->Destroy();
14930 g_pcurtain = NULL;
14931 }
14932
14933 g_brightness_init = false;
14934 return 1;
14935
14936#endif
14937
14938#ifdef BRIGHT_XCALIB
14939 if (g_brightness_init) {
14940 wxString cmd;
14941 cmd = "xcalib -clear";
14942 wxExecute(cmd, wxEXEC_ASYNC);
14943 g_brightness_init = false;
14944 }
14945
14946 return 1;
14947#endif
14948
14949 return 0;
14950}
14951
14952// Set brightness. [0..100]
14953int SetScreenBrightness(int brightness) {
14954#ifdef _WIN32
14955
14956 // Under Windows, we use the SetDeviceGammaRamp function which exists in
14957 // some (most modern?) versions of gdi32.dll Load the required library dll,
14958 // if not already in place
14959#ifdef ocpnUSE_GL
14960 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14961 if (g_pcurtain) {
14962 g_pcurtain->Close();
14963 g_pcurtain->Destroy();
14964 g_pcurtain = NULL;
14965 }
14966
14967 InitScreenBrightness();
14968
14969 if (NULL == hGDI32DLL) {
14970 // Unicode stuff.....
14971 wchar_t wdll_name[80];
14972 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
14973 LPCWSTR cstr = wdll_name;
14974
14975 hGDI32DLL = LoadLibrary(cstr);
14976
14977 if (NULL != hGDI32DLL) {
14978 // Get the entry points of the required functions
14979 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14980 hGDI32DLL, "SetDeviceGammaRamp");
14981 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14982 hGDI32DLL, "GetDeviceGammaRamp");
14983
14984 // If the functions are not found, unload the DLL and return false
14985 if ((NULL == g_pSetDeviceGammaRamp) ||
14986 (NULL == g_pGetDeviceGammaRamp)) {
14987 FreeLibrary(hGDI32DLL);
14988 hGDI32DLL = NULL;
14989 return 0;
14990 }
14991 }
14992 }
14993
14994 HDC hDC = GetDC(NULL); // Get the full screen DC
14995
14996 /*
14997 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
14998 if (cmcap != CM_GAMMA_RAMP)
14999 {
15000 wxLogMessage(_T(" Video hardware does not support brightness control by
15001 gamma ramp adjustment.")); return false;
15002 }
15003 */
15004
15005 int increment = brightness * 256 / 100;
15006
15007 // Build the Gamma Ramp table
15008 WORD GammaTable[3][256];
15009
15010 int table_val = 0;
15011 for (int i = 0; i < 256; i++) {
15012 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15013 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15014 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15015
15016 table_val += increment;
15017
15018 if (table_val > 65535) table_val = 65535;
15019 }
15020
15021 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15022 ReleaseDC(NULL, hDC); // Release the DC
15023
15024 return 1;
15025 }
15026#endif
15027
15028 {
15029 if (g_pSavedGammaMap) {
15030 HDC hDC = GetDC(NULL); // Get the full screen DC
15031 g_pSetDeviceGammaRamp(hDC,
15032 g_pSavedGammaMap); // Restore the saved ramp table
15033 ReleaseDC(NULL, hDC); // Release the DC
15034 }
15035
15036 if (brightness < 100) {
15037 if (NULL == g_pcurtain) InitScreenBrightness();
15038
15039 if (g_pcurtain) {
15040 int sbrite = wxMax(1, brightness);
15041 sbrite = wxMin(100, sbrite);
15042
15043 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15044 }
15045 } else {
15046 if (g_pcurtain) {
15047 g_pcurtain->Close();
15048 g_pcurtain->Destroy();
15049 g_pcurtain = NULL;
15050 }
15051 }
15052
15053 return 1;
15054 }
15055
15056#endif
15057
15058#ifdef BRIGHT_XCALIB
15059
15060 if (!g_brightness_init) {
15061 last_brightness = 100;
15062 g_brightness_init = true;
15063 temp_file_name = wxFileName::CreateTempFileName("");
15064 InitScreenBrightness();
15065 }
15066
15067#ifdef __OPCPN_USEICC__
15068 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15069 // desired, and then activate this temporary profile using xcalib <filename>
15070 if (!CreateSimpleICCProfileFile(
15071 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15072 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15073 wxString cmd(_T ( "xcalib " ));
15074 cmd += temp_file_name;
15075
15076 wxExecute(cmd, wxEXEC_ASYNC);
15077 }
15078
15079#else
15080 // Or, use "xcalib -co" to set overall contrast value
15081 // This is not as nice, since the -co parameter wants to be a fraction of
15082 // the current contrast, and values greater than 100 are not allowed. As a
15083 // result, increases of contrast must do a "-clear" step first, which
15084 // produces objectionable flashing.
15085 if (brightness > last_brightness) {
15086 wxString cmd;
15087 cmd = "xcalib -clear";
15088 wxExecute(cmd, wxEXEC_ASYNC);
15089
15090 ::wxMilliSleep(10);
15091
15092 int brite_adj = wxMax(1, brightness);
15093 cmd.Printf("xcalib -co %2d -a", brite_adj);
15094 wxExecute(cmd, wxEXEC_ASYNC);
15095 } else {
15096 int brite_adj = wxMax(1, brightness);
15097 int factor = (brite_adj * 100) / last_brightness;
15098 factor = wxMax(1, factor);
15099 wxString cmd;
15100 cmd.Printf("xcalib -co %2d -a", factor);
15101 wxExecute(cmd, wxEXEC_ASYNC);
15102 }
15103
15104#endif
15105
15106 last_brightness = brightness;
15107
15108#endif
15109
15110 return 0;
15111}
15112
15113#ifdef __OPCPN_USEICC__
15114
15115#define MLUT_TAG 0x6d4c5554L
15116#define VCGT_TAG 0x76636774L
15117
15118int GetIntEndian(unsigned char *s) {
15119 int ret;
15120 unsigned char *p;
15121 int i;
15122
15123 p = (unsigned char *)&ret;
15124
15125 if (1)
15126 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15127 else
15128 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15129
15130 return ret;
15131}
15132
15133unsigned short GetShortEndian(unsigned char *s) {
15134 unsigned short ret;
15135 unsigned char *p;
15136 int i;
15137
15138 p = (unsigned char *)&ret;
15139
15140 if (1)
15141 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15142 else
15143 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15144
15145 return ret;
15146}
15147
15148// Create a very simple Gamma correction file readable by xcalib
15149int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15150 double co_green, double co_blue) {
15151 FILE *fp;
15152
15153 if (file_name) {
15154 fp = fopen(file_name, "wb");
15155 if (!fp) return -1; /* file can not be created */
15156 } else
15157 return -1; /* filename char pointer not valid */
15158
15159 // Write header
15160 char header[128];
15161 for (int i = 0; i < 128; i++) header[i] = 0;
15162
15163 fwrite(header, 128, 1, fp);
15164
15165 // Num tags
15166 int numTags0 = 1;
15167 int numTags = GetIntEndian((unsigned char *)&numTags0);
15168 fwrite(&numTags, 1, 4, fp);
15169
15170 int tagName0 = VCGT_TAG;
15171 int tagName = GetIntEndian((unsigned char *)&tagName0);
15172 fwrite(&tagName, 1, 4, fp);
15173
15174 int tagOffset0 = 128 + 4 * sizeof(int);
15175 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15176 fwrite(&tagOffset, 1, 4, fp);
15177
15178 int tagSize0 = 1;
15179 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15180 fwrite(&tagSize, 1, 4, fp);
15181
15182 fwrite(&tagName, 1, 4, fp); // another copy of tag
15183
15184 fwrite(&tagName, 1, 4, fp); // dummy
15185
15186 // Table type
15187
15188 /* VideoCardGammaTable (The simplest type) */
15189 int gammatype0 = 0;
15190 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15191 fwrite(&gammatype, 1, 4, fp);
15192
15193 int numChannels0 = 3;
15194 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15195 fwrite(&numChannels, 1, 2, fp);
15196
15197 int numEntries0 = 256;
15198 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15199 fwrite(&numEntries, 1, 2, fp);
15200
15201 int entrySize0 = 1;
15202 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15203 fwrite(&entrySize, 1, 2, fp);
15204
15205 unsigned char ramp[256];
15206
15207 // Red ramp
15208 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15209 fwrite(ramp, 256, 1, fp);
15210
15211 // Green ramp
15212 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15213 fwrite(ramp, 256, 1, fp);
15214
15215 // Blue ramp
15216 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15217 fwrite(ramp, 256, 1, fp);
15218
15219 fclose(fp);
15220
15221 return 0;
15222}
15223#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:1194
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1193
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1194
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1193
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:13587
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:4391
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4387
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3486
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13546
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:4337
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:2222
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:7897
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7707
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:4915
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:4468
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5196
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:4412
bool IsTideDialogOpen() const
Definition chcanv.cpp:13585
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:4474
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13542
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4332
bool SetViewPoint(double lat, double lon)
Set the viewport center point.
Definition chcanv.cpp:5215
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10051
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 FontMgr.cpp:449
wxColour GetFontColor(const wxString &TextElement) const
Gets the text color for a UI element.
Definition FontMgr.cpp:117
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Gets a font object for a UI element.
Definition FontMgr.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:216
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:35
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)
Hooks into gui available in model.
bool g_b_overzoom_x
Allow high overzoom.
Definition gui_vars.cpp:42
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.