OpenCPN Partial API docs
Loading...
Searching...
No Matches
chcanv.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: Chart Canvas
5 * Author: David Register
6 *
7 ***************************************************************************
8 * Copyright (C) 2018 by David S. Register *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 **************************************************************************/
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 <wx/listimpl.cpp>
40
41#include "model/ais_decoder.h"
43#include "model/ais_target_data.h"
44#include "model/cmdline.h"
45#include "model/conn_params.h"
46#include "model/cutil.h"
47#include "model/geodesic.h"
48#include "model/gui.h"
49#include "model/idents.h"
50#include "model/multiplexer.h"
52#include "model/nav_object_database.h"
53#include "model/navobj_db.h"
54#include "model/navutil_base.h"
55#include "model/own_ship.h"
56#include "model/plugin_comm.h"
57#include "model/route.h"
58#include "model/routeman.h"
59#include "model/select.h"
60#include "model/select_item.h"
61#include "model/track.h"
62#include "model/wx28compat.h"
63
64#include "ais.h"
65#include "AISTargetAlertDialog.h"
66#include "CanvasConfig.h"
67#include "canvasMenu.h"
68#include "CanvasOptions.h"
69#include "chartdb.h"
70#include "chartimg.h"
71#include "chcanv.h"
72#include "ChInfoWin.h"
73#include "cm93.h" // for chart outline draw
74#include "compass.h"
75#include "concanv.h"
76#include "displays.h"
77#include "hotkeys_dlg.h"
78#include "FontMgr.h"
79#include "glTextureDescriptor.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 "route_validator.h"
101#include "RoutePropDlgImpl.h"
102#include "s52plib.h"
103#include "s52utils.h"
104#include "s57chart.h" // for ArrayOfS57Obj
105#include "SendToGpsDlg.h"
106#include "shapefile_basemap.h"
107#include "styles.h"
108#include "SystemCmdSound.h"
109#include "tcmgr.h"
110#include "TCWin.h"
111#include "thumbwin.h"
112#include "tide_time.h"
113#include "timers.h"
114#include "toolbar.h"
115#include "track_gui.h"
116#include "TrackPropDlg.h"
117#include "undo.h"
118
119#include "s57_ocpn_utils.h"
120
121#ifdef __ANDROID__
122#include "androidUTIL.h"
123#endif
124
125#ifdef ocpnUSE_GL
126#include "glChartCanvas.h"
127#include "notification_manager_gui.h"
129#endif
130
131#ifdef __VISUALC__
132#include <wx/msw/msvcrt.h>
133#endif
134
135#ifndef __WXMSW__
136#include <signal.h>
137#include <setjmp.h>
138
139#endif
140
141extern float g_ShipScaleFactorExp;
142extern double g_mouse_zoom_sensitivity;
143
144#include <vector>
145
146#ifdef __WXMSW__
147#define printf printf2
148
149int __cdecl printf2(const char *format, ...) {
150 char str[1024];
151
152 va_list argptr;
153 va_start(argptr, format);
154 int ret = vsnprintf(str, sizeof(str), format, argptr);
155 va_end(argptr);
156 OutputDebugStringA(str);
157 return ret;
158}
159#endif
160
161#if defined(__MSVC__) && (_MSC_VER < 1700)
162#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
163#endif
164
165// Define to enable the invocation of a temporary menubar by pressing the Alt
166// key. Not implemented for Windows XP, as it interferes with Alt-Tab
167// processing.
168#define OCPN_ALT_MENUBAR 1
169
170// Profiling support
171// #include "/usr/include/valgrind/callgrind.h"
172
173// ----------------------------------------------------------------------------
174// Useful Prototypes
175// ----------------------------------------------------------------------------
176extern bool G_FloatPtInPolygon(MyFlPoint *rgpts, int wnumpts, float x, float y);
177extern void catch_signals(int signo);
178
179extern void AlphaBlending(ocpnDC &dc, int x, int y, int size_x, int size_y,
180 float radius, wxColour color,
181 unsigned char transparency);
182
183extern double g_ChartNotRenderScaleFactor;
184extern ChartDB *ChartData;
185extern bool bDBUpdateInProgress;
186extern ColorScheme global_color_scheme;
187extern int g_nbrightness;
188
189extern ConsoleCanvas *console;
190extern OCPNPlatform *g_Platform;
191
192extern RouteList *pRouteList;
193extern std::vector<Track *> g_TrackList;
194extern MyConfig *pConfig;
195extern Routeman *g_pRouteMan;
196extern ThumbWin *pthumbwin;
197extern TCMgr *ptcmgr;
198extern Select *pSelectTC;
199extern MarkInfoDlg *g_pMarkInfoDialog;
200extern RoutePropDlgImpl *pRoutePropDialog;
201extern TrackPropDlg *pTrackPropDialog;
202extern ActiveTrack *g_pActiveTrack;
203
204extern double AnchorPointMinDist;
205extern bool AnchorAlertOn1;
206extern bool AnchorAlertOn2;
207extern int g_nAWMax;
208
209extern RouteManagerDialog *pRouteManagerDialog;
210extern GoToPositionDialog *pGoToPositionDialog;
211extern wxString GetLayerName(int id);
212extern wxString g_uploadConnection;
213extern bool g_bsimplifiedScalebar;
214
215extern bool bDrawCurrentValues;
216
217extern s52plib *ps52plib;
218
219extern bool g_bTempShowMenuBar;
220extern bool g_bShowMenuBar;
221extern bool g_bShowCompassWin;
222
223extern MyFrame *gFrame;
224extern options *g_options;
225
226extern int g_iNavAidRadarRingsNumberVisible;
227extern bool g_bNavAidRadarRingsShown;
228extern float g_fNavAidRadarRingsStep;
229extern int g_pNavAidRadarRingsStepUnits;
230extern bool g_bWayPointPreventDragging;
231extern bool g_bEnableZoomToCursor;
232extern bool g_bShowChartBar;
233extern int g_ENCSoundingScaleFactor;
234extern int g_ENCTextScaleFactor;
235extern int g_maxzoomin;
236
237bool g_bShowShipToActive;
238int g_shipToActiveStyle;
239int g_shipToActiveColor;
240
241extern AISTargetQueryDialog *g_pais_query_dialog_active;
242
243extern int g_S57_dialog_sx, g_S57_dialog_sy;
244
245extern PopUpDSlide *pPopupDetailSlider;
246extern int g_detailslider_dialog_x, g_detailslider_dialog_y;
247
248extern bool g_b_overzoom_x; // Allow high overzoom
249extern double g_plus_minus_zoom_factor;
250
251extern int g_OwnShipIconType;
252extern double g_n_ownship_length_meters;
253extern double g_n_ownship_beam_meters;
254extern double g_n_gps_antenna_offset_y;
255extern double g_n_gps_antenna_offset_x;
256extern int g_n_ownship_min_mm;
257
258extern double g_COGAvg; // only needed for debug....
259
260extern int g_click_stop;
261
262extern double g_ownship_predictor_minutes;
263extern int g_cog_predictor_style;
264extern wxString g_cog_predictor_color;
265extern int g_cog_predictor_endmarker;
266extern int g_ownship_HDTpredictor_style;
267extern wxString g_ownship_HDTpredictor_color;
268extern int g_ownship_HDTpredictor_endmarker;
269extern int g_ownship_HDTpredictor_width;
270extern double g_ownship_HDTpredictor_miles;
271
272extern bool g_bquiting;
273extern AISTargetListDialog *g_pAISTargetList;
274
275extern PlugInManager *g_pi_manager;
276
277extern OCPN_AUIManager *g_pauimgr;
278
279extern bool g_bopengl;
280
281extern bool g_bFullScreenQuilt;
282
283extern bool g_bsmoothpanzoom;
284extern bool g_bSmoothRecenter;
285
286bool g_bDebugOGL;
287
288extern bool g_b_assume_azerty;
289
290extern ChartGroupArray *g_pGroupArray;
291
292extern S57QueryDialog *g_pObjectQueryDialog;
293extern ocpnStyle::StyleManager *g_StyleManager;
294
295extern OcpnSound *g_anchorwatch_sound;
296
297extern bool g_bresponsive;
298extern int g_chart_zoom_modifier_raster;
299extern int g_chart_zoom_modifier_vector;
300extern int g_ChartScaleFactor;
301
302#ifdef ocpnUSE_GL
303#endif
304
305extern double g_gl_ms_per_frame;
306extern bool g_benable_rotate;
307extern bool g_bRollover;
308
309extern bool g_bSpaceDropMark;
310extern bool g_bAutoHideToolbar;
311extern int g_nAutoHideToolbar;
312extern bool g_bDeferredInitDone;
313
314extern wxString g_CmdSoundString;
315ShapeBaseChartSet gShapeBasemap;
316extern bool g_CanvasHideNotificationIcon;
317
318// TODO why are these static?
319
329static int mouse_x;
339static int mouse_y;
340static bool mouse_leftisdown;
341
342bool g_brouteCreating;
343
344bool g_bShowTrackPointTime;
345
346int r_gamma_mult;
347int g_gamma_mult;
348int b_gamma_mult;
349int gamma_state;
350bool g_brightness_init;
351int last_brightness;
352
353int g_cog_predictor_width;
354extern double g_display_size_mm;
355
356extern ocpnFloatingToolbarDialog *g_MainToolbar;
357extern iENCToolbar *g_iENCToolbar;
358extern wxColour g_colourOwnshipRangeRingsColour;
359
360// LIVE ETA OPTION
361bool g_bShowLiveETA;
362extern double g_defaultBoatSpeed;
363double g_defaultBoatSpeedUserUnit;
364
365extern int g_nAIS_activity_timer;
366extern bool g_bskew_comp;
367extern float g_compass_scalefactor;
368extern int g_COGAvgSec; // COG average period (sec.) for Course Up Mode
369extern bool g_btenhertz;
370
371wxGLContext *g_pGLcontext; // shared common context
372
373extern bool g_useMUI;
374extern unsigned int g_canvasConfig;
375
376extern ChartCanvas *g_focusCanvas;
377extern ChartCanvas *g_overlayCanvas;
378
379extern float g_toolbar_scalefactor;
380extern SENCThreadManager *g_SencThreadManager;
381
382wxString g_ObjQFileExt;
383
384// "Curtain" mode parameters
385wxDialog *g_pcurtain;
386
387extern int g_GUIScaleFactor;
388// Win DPI scale factor
389double g_scaler;
390wxString g_lastS52PLIBPluginMessage;
391extern bool g_bChartBarEx;
392bool g_PrintingInProgress;
393
394#define MIN_BRIGHT 10
395#define MAX_BRIGHT 100
396
397//------------------------------------------------------------------------------
398// ChartCanvas Implementation
399//------------------------------------------------------------------------------
400BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
401EVT_PAINT(ChartCanvas::OnPaint)
402EVT_ACTIVATE(ChartCanvas::OnActivate)
403EVT_SIZE(ChartCanvas::OnSize)
404#ifndef HAVE_WX_GESTURE_EVENTS
405EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
406#endif
407EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
408EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
409EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
410EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
411EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
412EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
413EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
414EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
415EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
416EVT_KEY_UP(ChartCanvas::OnKeyUp)
417EVT_CHAR(ChartCanvas::OnKeyChar)
418EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
419EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
420EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
421EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
422EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
423EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
424EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
425EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
426
427END_EVENT_TABLE()
428
429// Define a constructor for my canvas
430ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
431 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
432 m_nmea_log(nmea_log) {
433 parent_frame = (MyFrame *)frame; // save a pointer to parent
434 m_canvasIndex = canvasIndex;
435
436 pscratch_bm = NULL;
437
438 SetBackgroundColour(wxColour(0, 0, 0));
439 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
440 // color scheme change
441
442 m_groupIndex = 0;
443 m_bDrawingRoute = false;
444 m_bRouteEditing = false;
445 m_bMarkEditing = false;
446 m_bRoutePoinDragging = false;
447 m_bIsInRadius = false;
448 m_bMayToggleMenuBar = true;
449
450 m_bFollow = false;
451 m_bShowNavobjects = true;
452 m_bTCupdate = false;
453 m_bAppendingRoute = false; // was true in MSW, why??
454 pThumbDIBShow = NULL;
455 m_bShowCurrent = false;
456 m_bShowTide = false;
457 bShowingCurrent = false;
458 pCwin = NULL;
459 warp_flag = false;
460 m_bzooming = false;
461 m_b_paint_enable = true;
462 m_routeState = 0;
463
464 pss_overlay_bmp = NULL;
465 pss_overlay_mask = NULL;
466 m_bChartDragging = false;
467 m_bMeasure_Active = false;
468 m_bMeasure_DistCircle = false;
469 m_pMeasureRoute = NULL;
470 m_pTrackRolloverWin = NULL;
471 m_pRouteRolloverWin = NULL;
472 m_pAISRolloverWin = NULL;
473 m_bedge_pan = false;
474 m_disable_edge_pan = false;
475 m_dragoffsetSet = false;
476 m_bautofind = false;
477 m_bFirstAuto = true;
478 m_groupIndex = 0;
479 m_singleChart = NULL;
480 m_upMode = NORTH_UP_MODE;
481 m_bShowAIS = true;
482 m_bShowAISScaled = false;
483 m_timed_move_vp_active = false;
484
485 m_vLat = 0.;
486 m_vLon = 0.;
487
488 m_pCIWin = NULL;
489
490 m_pSelectedRoute = NULL;
491 m_pSelectedTrack = NULL;
492 m_pRoutePointEditTarget = NULL;
493 m_pFoundPoint = NULL;
494 m_pMouseRoute = NULL;
495 m_prev_pMousePoint = NULL;
496 m_pEditRouteArray = NULL;
497 m_pFoundRoutePoint = NULL;
498 m_FinishRouteOnKillFocus = true;
499
500 m_pRolloverRouteSeg = NULL;
501 m_pRolloverTrackSeg = NULL;
502 m_bsectors_shown = false;
503
504 m_bbrightdir = false;
505 r_gamma_mult = 1;
506 g_gamma_mult = 1;
507 b_gamma_mult = 1;
508
509 m_pos_image_user_day = NULL;
510 m_pos_image_user_dusk = NULL;
511 m_pos_image_user_night = NULL;
512 m_pos_image_user_grey_day = NULL;
513 m_pos_image_user_grey_dusk = NULL;
514 m_pos_image_user_grey_night = NULL;
515
516 m_zoom_factor = 1;
517 m_rotation_speed = 0;
518 m_mustmove = 0;
519
520 m_OSoffsetx = 0.;
521 m_OSoffsety = 0.;
522
523 m_pos_image_user_yellow_day = NULL;
524 m_pos_image_user_yellow_dusk = NULL;
525 m_pos_image_user_yellow_night = NULL;
526
527 SetOwnShipState(SHIP_INVALID);
528
529 undo = new Undo(this);
530
531 VPoint.Invalidate();
532
533 m_glcc = NULL;
534
535 m_focus_indicator_pix = 1;
536
537 m_pCurrentStack = NULL;
538 m_bpersistent_quilt = false;
539 m_piano_ctx_menu = NULL;
540 m_Compass = NULL;
541 m_NotificationsList = NULL;
542 m_notification_button = NULL;
543
544 g_ChartNotRenderScaleFactor = 2.0;
545 m_bShowScaleInStatusBar = true;
546
547 m_muiBar = NULL;
548 m_bShowScaleInStatusBar = false;
549 m_show_focus_bar = true;
550
551 m_bShowOutlines = false;
552 m_bDisplayGrid = false;
553 m_bShowDepthUnits = true;
554 m_encDisplayCategory = (int)STANDARD;
555
556 m_encShowLights = true;
557 m_encShowAnchor = true;
558 m_encShowDataQual = false;
559 m_bShowGPS = true;
560 m_pQuilt = new Quilt(this);
561 SetQuiltMode(true);
562 SetAlertString(_T(""));
563 m_sector_glat = 0;
564 m_sector_glon = 0;
565 g_PrintingInProgress = false;
566
567#ifdef HAVE_WX_GESTURE_EVENTS
568 m_oldVPSScale = -1.0;
569 m_popupWanted = false;
570 m_leftdown = false;
571#endif /* HAVE_WX_GESTURE_EVENTS */
572
573 SetupGlCanvas();
574
575 singleClickEventIsValid = false;
576
577 // Build the cursors
578
579 pCursorLeft = NULL;
580 pCursorRight = NULL;
581 pCursorUp = NULL;
582 pCursorDown = NULL;
583 pCursorArrow = NULL;
584 pCursorPencil = NULL;
585 pCursorCross = NULL;
586
587 RebuildCursors();
588
589 SetCursor(*pCursorArrow);
590
591 pPanTimer = new wxTimer(this, m_MouseDragging);
592 pPanTimer->Stop();
593
594 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
595 pMovementTimer->Stop();
596
597 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
598 pMovementStopTimer->Stop();
599
600 pRotDefTimer = new wxTimer(this, ROT_TIMER);
601 pRotDefTimer->Stop();
602
603 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
604 m_DoubleClickTimer->Stop();
605
606 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
607 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
608 m_chart_drag_inertia_active = false;
609
610 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
611 m_animationActive = false;
612
613 m_panx = m_pany = 0;
614 m_panspeed = 0;
615 m_panx_target_final = m_pany_target_final = 0;
616 m_panx_target_now = m_pany_target_now = 0;
617
618 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
619 pCurTrackTimer->Stop();
620 m_curtrack_timer_msec = 10;
621
622 m_wheelzoom_stop_oneshot = 0;
623 m_last_wheel_dir = 0;
624
625 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
626
627 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
628
629 m_rollover_popup_timer_msec = 20;
630
631 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
632
633 m_b_rot_hidef = true;
634
635 proute_bm = NULL;
636 m_prot_bm = NULL;
637
638 m_upMode = NORTH_UP_MODE;
639 m_bLookAhead = false;
640
641 // Set some benign initial values
642
643 m_cs = GLOBAL_COLOR_SCHEME_DAY;
644 VPoint.clat = 0;
645 VPoint.clon = 0;
646 VPoint.view_scale_ppm = 1;
647 VPoint.Invalidate();
648 m_nMeasureState = 0;
649
650 m_canvas_scale_factor = 1.;
651
652 m_canvas_width = 1000;
653
654 m_overzoomTextWidth = 0;
655 m_overzoomTextHeight = 0;
656
657 // Create the default world chart
658 pWorldBackgroundChart = new GSHHSChart;
659 gShapeBasemap.Reset();
660
661 // Create the default depth unit emboss maps
662 m_pEM_Feet = NULL;
663 m_pEM_Meters = NULL;
664 m_pEM_Fathoms = NULL;
665
666 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
667
668 m_pEM_OverZoom = NULL;
669 SetOverzoomFont();
670 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
671
672 // Build icons for tide/current points
673 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
674 m_bmTideDay = style->GetIconScaled(_T("tidesml"),
675 1. / g_Platform->GetDisplayDIPMult(this));
676
677 // Dusk
678 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
679
680 // Night
681 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
682
683 // Build Dusk/Night ownship icons
684 double factor_dusk = 0.5;
685 double factor_night = 0.25;
686
687 // Red
688 m_os_image_red_day = style->GetIcon(_T("ship-red")).ConvertToImage();
689
690 int rimg_width = m_os_image_red_day.GetWidth();
691 int rimg_height = m_os_image_red_day.GetHeight();
692
693 m_os_image_red_dusk = m_os_image_red_day.Copy();
694 m_os_image_red_night = m_os_image_red_day.Copy();
695
696 for (int iy = 0; iy < rimg_height; iy++) {
697 for (int ix = 0; ix < rimg_width; ix++) {
698 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
699 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
700 m_os_image_red_day.GetGreen(ix, iy),
701 m_os_image_red_day.GetBlue(ix, iy));
702 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
703 hsv.value = hsv.value * factor_dusk;
704 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
705 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
706
707 hsv = wxImage::RGBtoHSV(rgb);
708 hsv.value = hsv.value * factor_night;
709 nrgb = wxImage::HSVtoRGB(hsv);
710 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
711 }
712 }
713 }
714
715 // Grey
716 m_os_image_grey_day =
717 style->GetIcon(_T("ship-red")).ConvertToImage().ConvertToGreyscale();
718
719 int gimg_width = m_os_image_grey_day.GetWidth();
720 int gimg_height = m_os_image_grey_day.GetHeight();
721
722 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
723 m_os_image_grey_night = m_os_image_grey_day.Copy();
724
725 for (int iy = 0; iy < gimg_height; iy++) {
726 for (int ix = 0; ix < gimg_width; ix++) {
727 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
728 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
729 m_os_image_grey_day.GetGreen(ix, iy),
730 m_os_image_grey_day.GetBlue(ix, iy));
731 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
732 hsv.value = hsv.value * factor_dusk;
733 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
734 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
735
736 hsv = wxImage::RGBtoHSV(rgb);
737 hsv.value = hsv.value * factor_night;
738 nrgb = wxImage::HSVtoRGB(hsv);
739 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
740 }
741 }
742 }
743
744 // Yellow
745 m_os_image_yellow_day = m_os_image_red_day.Copy();
746
747 gimg_width = m_os_image_yellow_day.GetWidth();
748 gimg_height = m_os_image_yellow_day.GetHeight();
749
750 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
751 m_os_image_yellow_night = m_os_image_red_day.Copy();
752
753 for (int iy = 0; iy < gimg_height; iy++) {
754 for (int ix = 0; ix < gimg_width; ix++) {
755 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
756 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
757 m_os_image_yellow_day.GetGreen(ix, iy),
758 m_os_image_yellow_day.GetBlue(ix, iy));
759 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
760 hsv.hue += 60. / 360.; // shift to yellow
761 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
762 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
763
764 hsv = wxImage::RGBtoHSV(rgb);
765 hsv.value = hsv.value * factor_dusk;
766 hsv.hue += 60. / 360.; // shift to yellow
767 nrgb = wxImage::HSVtoRGB(hsv);
768 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
769
770 hsv = wxImage::RGBtoHSV(rgb);
771 hsv.hue += 60. / 360.; // shift to yellow
772 hsv.value = hsv.value * factor_night;
773 nrgb = wxImage::HSVtoRGB(hsv);
774 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
775 }
776 }
777 }
778
779 // Set initial pointers to ownship images
780 m_pos_image_red = &m_os_image_red_day;
781 m_pos_image_yellow = &m_os_image_yellow_day;
782 m_pos_image_grey = &m_os_image_grey_day;
783
784 SetUserOwnship();
785
786 m_pBrightPopup = NULL;
787
788#ifdef ocpnUSE_GL
789 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
790#endif
791
792 SetupGridFont();
793
794 m_Piano = new Piano(this);
795
796 m_bShowCompassWin = true;
797 m_Compass = new ocpnCompass(this);
798 m_Compass->SetScaleFactor(g_compass_scalefactor);
799 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
800
801 m_notification_button = new NotificationButton(this);
802 m_notification_button->SetScaleFactor(g_compass_scalefactor);
803 m_notification_button->Show(true);
804
805 m_pianoFrozen = false;
806
807 SetMinSize(wxSize(200, 200));
808
809 m_displayScale = 1.0;
810#if defined(__WXOSX__) || defined(__WXGTK3__)
811 // Support scaled HDPI displays.
812 m_displayScale = GetContentScaleFactor();
813#endif
814 VPoint.SetPixelScale(m_displayScale);
815
816#ifdef HAVE_WX_GESTURE_EVENTS
817 // if (!m_glcc)
818 {
819 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
820 wxLogError("Failed to enable touch events");
821 }
822
823 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
824
825 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
826 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
827
828 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
829 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
830
831 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
832 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
833
834 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
835 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
836 }
837#endif
838
839 // Listen for notification events
840 auto &noteman = NotificationManager::GetInstance();
841
842 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
843 evt_notificationlist_change_listener.Listen(
844 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
845 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
846 if (m_NotificationsList && m_NotificationsList->IsShown()) {
847 m_NotificationsList->ReloadNotificationList();
848 }
849 Refresh();
850 });
851}
852
853ChartCanvas::~ChartCanvas() {
854 delete pThumbDIBShow;
855
856 // Delete Cursors
857 delete pCursorLeft;
858 delete pCursorRight;
859 delete pCursorUp;
860 delete pCursorDown;
861 delete pCursorArrow;
862 delete pCursorPencil;
863 delete pCursorCross;
864
865 delete pPanTimer;
866 delete pMovementTimer;
867 delete pMovementStopTimer;
868 delete pCurTrackTimer;
869 delete pRotDefTimer;
870 delete m_DoubleClickTimer;
871
872 delete m_pTrackRolloverWin;
873 delete m_pRouteRolloverWin;
874 delete m_pAISRolloverWin;
875 delete m_pBrightPopup;
876
877 delete m_pCIWin;
878
879 delete pscratch_bm;
880
881 m_dc_route.SelectObject(wxNullBitmap);
882 delete proute_bm;
883
884 delete pWorldBackgroundChart;
885 delete pss_overlay_bmp;
886
887 delete m_pEM_Feet;
888 delete m_pEM_Meters;
889 delete m_pEM_Fathoms;
890
891 delete m_pEM_OverZoom;
892 // delete m_pEM_CM93Offset;
893
894 delete m_prot_bm;
895
896 delete m_pos_image_user_day;
897 delete m_pos_image_user_dusk;
898 delete m_pos_image_user_night;
899 delete m_pos_image_user_grey_day;
900 delete m_pos_image_user_grey_dusk;
901 delete m_pos_image_user_grey_night;
902 delete m_pos_image_user_yellow_day;
903 delete m_pos_image_user_yellow_dusk;
904 delete m_pos_image_user_yellow_night;
905
906 delete undo;
907#ifdef ocpnUSE_GL
908 if (!g_bdisable_opengl) {
909 delete m_glcc;
910
911#if wxCHECK_VERSION(2, 9, 0)
912 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
913#endif
914 }
915#endif
916
917 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
918 // wx tries to deliver events to this canvas during destroy.
919 MUIBar *muiBar = m_muiBar;
920 m_muiBar = 0;
921 delete muiBar;
922 delete m_pQuilt;
923 delete m_pCurrentStack;
924 delete m_Compass;
925 delete m_Piano;
926}
927
928void ChartCanvas::SetupGridFont() {
929 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
930 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
931 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
932 m_pgridFont = FontMgr::Get().FindOrCreateFont(
933 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
934 FALSE, wxString(_T ( "Arial" )));
935}
936
937void ChartCanvas::RebuildCursors() {
938 delete pCursorLeft;
939 delete pCursorRight;
940 delete pCursorUp;
941 delete pCursorDown;
942 delete pCursorArrow;
943 delete pCursorPencil;
944 delete pCursorCross;
945
946 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
947 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
948
949 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
950
951 wxImage ICursorLeft = style->GetIcon(_T("left")).ConvertToImage();
952 wxImage ICursorRight = style->GetIcon(_T("right")).ConvertToImage();
953 wxImage ICursorUp = style->GetIcon(_T("up")).ConvertToImage();
954 wxImage ICursorDown = style->GetIcon(_T("down")).ConvertToImage();
955 wxImage ICursorPencil =
956 style->GetIconScaled(_T("pencil"), pencilScale).ConvertToImage();
957 wxImage ICursorCross = style->GetIcon(_T("cross")).ConvertToImage();
958
959#if !defined(__WXMSW__) && !defined(__WXQT__)
960 ICursorLeft.ConvertAlphaToMask(128);
961 ICursorRight.ConvertAlphaToMask(128);
962 ICursorUp.ConvertAlphaToMask(128);
963 ICursorDown.ConvertAlphaToMask(128);
964 ICursorPencil.ConvertAlphaToMask(10);
965 ICursorCross.ConvertAlphaToMask(10);
966#endif
967
968 if (ICursorLeft.Ok()) {
969 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
970 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
971 pCursorLeft = new wxCursor(ICursorLeft);
972 } else
973 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
974
975 if (ICursorRight.Ok()) {
976 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
977 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
978 pCursorRight = new wxCursor(ICursorRight);
979 } else
980 pCursorRight = new wxCursor(wxCURSOR_ARROW);
981
982 if (ICursorUp.Ok()) {
983 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
984 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
985 pCursorUp = new wxCursor(ICursorUp);
986 } else
987 pCursorUp = new wxCursor(wxCURSOR_ARROW);
988
989 if (ICursorDown.Ok()) {
990 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
991 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
992 pCursorDown = new wxCursor(ICursorDown);
993 } else
994 pCursorDown = new wxCursor(wxCURSOR_ARROW);
995
996 if (ICursorPencil.Ok()) {
997 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
998 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
999 pCursorPencil = new wxCursor(ICursorPencil);
1000 } else
1001 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
1002
1003 if (ICursorCross.Ok()) {
1004 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
1005 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
1006 pCursorCross = new wxCursor(ICursorCross);
1007 } else
1008 pCursorCross = new wxCursor(wxCURSOR_ARROW);
1009
1010 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
1011 pPlugIn_Cursor = NULL;
1012}
1013
1014void ChartCanvas::CanvasApplyLocale() {
1015 CreateDepthUnitEmbossMaps(m_cs);
1016 CreateOZEmbossMapData(m_cs);
1017}
1018
1019void ChartCanvas::SetupGlCanvas() {
1020#ifndef __ANDROID__
1021#ifdef ocpnUSE_GL
1022 if (!g_bdisable_opengl) {
1023 if (g_bopengl) {
1024 wxLogMessage(_T("Creating glChartCanvas"));
1025 m_glcc = new glChartCanvas(this);
1026
1027 // We use one context for all GL windows, so that textures etc will be
1028 // automatically shared
1029 if (IsPrimaryCanvas()) {
1030 // qDebug() << "Creating Primary Context";
1031
1032 // wxGLContextAttrs ctxAttr;
1033 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
1034 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
1035 // NULL, &ctxAttr);
1036 wxGLContext *pctx = new wxGLContext(m_glcc);
1037 m_glcc->SetContext(pctx);
1038 g_pGLcontext = pctx; // Save a copy of the common context
1039 } else {
1040#ifdef __WXOSX__
1041 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
1042#else
1043 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
1044 // saved common context
1045#endif
1046 }
1047 }
1048 }
1049#endif
1050#endif
1051
1052#ifdef __ANDROID__ // ocpnUSE_GL
1053 if (!g_bdisable_opengl) {
1054 if (g_bopengl) {
1055 // qDebug() << "SetupGlCanvas";
1056 wxLogMessage(_T("Creating glChartCanvas"));
1057
1058 // We use one context for all GL windows, so that textures etc will be
1059 // automatically shared
1060 if (IsPrimaryCanvas()) {
1061 qDebug() << "Creating Primary glChartCanvas";
1062
1063 // wxGLContextAttrs ctxAttr;
1064 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
1065 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
1066 // NULL, &ctxAttr);
1067 m_glcc = new glChartCanvas(this);
1068
1069 wxGLContext *pctx = new wxGLContext(m_glcc);
1070 m_glcc->SetContext(pctx);
1071 g_pGLcontext = pctx; // Save a copy of the common context
1072 m_glcc->m_pParentCanvas = this;
1073 // m_glcc->Reparent(this);
1074 } else {
1075 qDebug() << "Creating Secondary glChartCanvas";
1076 // QGLContext *pctx =
1077 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
1078 // << "pctx: " << pctx;
1079
1080 m_glcc = new glChartCanvas(
1081 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
1082 // m_glcc = new glChartCanvas(this, pctx); //Shared
1083 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
1084 wxGLContext *pwxctx = new wxGLContext(m_glcc);
1085 m_glcc->SetContext(pwxctx);
1086 m_glcc->m_pParentCanvas = this;
1087 // m_glcc->Reparent(this);
1088 }
1089 }
1090 }
1091#endif
1092}
1093
1094void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
1095 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1096
1097 // On Android, we get a KillFocus on just about every keystroke.
1098 // Why?
1099#ifdef __ANDROID__
1100 return;
1101#endif
1102
1103 // Special logic:
1104 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
1105 // canvas focus. Why??? Who knows... So, we provide for this case by
1106 // starting a timer if required to actually Finish() a route on a legitimate
1107 // focus change, but not if the focus is quickly regained ( <20 msec.) on
1108 // this canvas.
1109#ifdef __WXOSX__
1110 if (m_routeState && m_FinishRouteOnKillFocus)
1111 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
1112#else
1113 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1114#endif
1115}
1116
1117void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
1118 m_routeFinishTimer.Stop();
1119
1120 // Try to keep the global top-line menubar selections up to date with the
1121 // current "focus" canvas
1122 gFrame->UpdateGlobalMenuItems(this);
1123
1124 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1125}
1126
1127void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
1128 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1129}
1130
1131#ifdef HAVE_WX_GESTURE_EVENTS
1132void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
1133 /* we defer the popup menu call upon the leftup event
1134 else the menu disappears immediately,
1135 (see
1136 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
1137 */
1138 m_popupWanted = true;
1139}
1140
1141void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
1142 // not implemented yet
1143}
1144
1145void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1146
1147void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1148
1149void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1150 wxPoint pos = event.GetPosition();
1151
1152 m_leftdown = false;
1153
1154 if (!m_popupWanted) {
1155 wxMouseEvent ev(wxEVT_LEFT_UP);
1156 ev.m_x = pos.x;
1157 ev.m_y = pos.y;
1158 MouseEvent(ev);
1159 return;
1160 }
1161
1162 m_popupWanted = false;
1163
1164 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1165 ev.m_x = pos.x;
1166 ev.m_y = pos.y;
1167
1168 MouseEvent(ev);
1169}
1170
1171void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1172 m_leftdown = true;
1173
1174 wxPoint pos = event.GetPosition();
1175 MouseEvent(event);
1176}
1177
1178void ChartCanvas::OnMotion(wxMouseEvent &event) {
1179 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1180 dragging, upon simple click, and without the OnLeftDown event before Thus,
1181 this consists in skiping it, and setting the leftdown bit according to a
1182 status that we trust */
1183 event.m_leftDown = m_leftdown;
1184 MouseEvent(event);
1185}
1186
1187void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1188 /* there are spurious end zoom events upon right-click */
1189 if (event.IsGestureEnd()) return;
1190
1191 double factor = event.GetZoomFactor();
1192
1193 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1194 m_oldVPSScale = GetVPScale();
1195 }
1196
1197 double current_vps = GetVPScale();
1198 double wanted_factor = m_oldVPSScale / current_vps * factor;
1199
1200 ZoomCanvas(wanted_factor, true, false);
1201
1202 // Allow combined zoom/pan operation
1203 if (event.IsGestureStart()) {
1204 m_zoomStartPoint = event.GetPosition();
1205 } else {
1206 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1207 PanCanvas(-delta.x, -delta.y);
1208 m_zoomStartPoint = event.GetPosition();
1209 }
1210}
1211
1212void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1213
1214void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1215 DoRotateCanvas(0.0);
1216}
1217#endif /* HAVE_WX_GESTURE_EVENTS */
1218
1219void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1220 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1221 m_vLat = pcc->iLat;
1222 m_vLon = pcc->iLon;
1223
1224 m_restore_dbindex = pcc->DBindex;
1225 m_bFollow = pcc->bFollow;
1226 if (pcc->GroupID < 0) pcc->GroupID = 0;
1227
1228 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1229 m_groupIndex = 0;
1230 else
1231 m_groupIndex = pcc->GroupID;
1232
1233 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1234
1235 ShowTides(pcc->bShowTides);
1236 ShowCurrents(pcc->bShowCurrents);
1237
1238 SetShowDepthUnits(pcc->bShowDepthUnits);
1239 SetShowGrid(pcc->bShowGrid);
1240 SetShowOutlines(pcc->bShowOutlines);
1241
1242 SetShowAIS(pcc->bShowAIS);
1243 SetAttenAIS(pcc->bAttenAIS);
1244
1245 // ENC options
1246 SetShowENCText(pcc->bShowENCText);
1247 m_encDisplayCategory = pcc->nENCDisplayCategory;
1248 m_encShowDepth = pcc->bShowENCDepths;
1249 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1250 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1251 m_encShowLights = pcc->bShowENCLights;
1252 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1253 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1254 m_encShowDataQual = pcc->bShowENCDataQuality;
1255
1256 bool courseUp = pcc->bCourseUp;
1257 bool headUp = pcc->bHeadUp;
1258 m_upMode = NORTH_UP_MODE;
1259 if (courseUp)
1260 m_upMode = COURSE_UP_MODE;
1261 else if (headUp)
1262 m_upMode = HEAD_UP_MODE;
1263
1264 m_bLookAhead = pcc->bLookahead;
1265
1266 m_singleChart = NULL;
1267}
1268
1269void ChartCanvas::ApplyGlobalSettings() {
1270 // GPS compas window
1271 if (m_Compass) {
1272 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1273 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1274 }
1275 m_notification_button->UpdateStatus();
1276}
1277
1278void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1279 bool groupOK = CheckGroup(m_groupIndex);
1280
1281 if (!groupOK) {
1282 SetGroupIndex(m_groupIndex, true);
1283 }
1284}
1285
1286void ChartCanvas::SetShowGPS(bool bshow) {
1287 if (m_bShowGPS != bshow) {
1288 delete m_Compass;
1289 m_Compass = new ocpnCompass(this, bshow);
1290 m_Compass->SetScaleFactor(g_compass_scalefactor);
1291 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1292 }
1293 m_bShowGPS = bshow;
1294}
1295
1296void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1297 m_bShowCompassWin = bshow;
1298 if (m_Compass) {
1299 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1300 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1301 }
1302}
1303
1304int ChartCanvas::GetPianoHeight() {
1305 int height = 0;
1306 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1307
1308 return height;
1309}
1310
1311void ChartCanvas::ConfigureChartBar() {
1312 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1313
1314 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
1315 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
1316
1317 if (GetQuiltMode()) {
1318 m_Piano->SetRoundedRectangles(true);
1319 }
1320 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
1321 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon(_T("polyprj"))));
1322 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
1323}
1324
1325void ChartCanvas::ShowTides(bool bShow) {
1326 gFrame->LoadHarmonics();
1327
1328 if (ptcmgr->IsReady()) {
1329 SetbShowTide(bShow);
1330
1331 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1332 } else {
1333 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1334 SetbShowTide(false);
1335 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1336 }
1337
1338 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1339 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1340
1341 // TODO
1342 // if( GetbShowTide() ) {
1343 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1344 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1345 // update
1346 // } else
1347 // FrameTCTimer.Stop();
1348}
1349
1350void ChartCanvas::ShowCurrents(bool bShow) {
1351 gFrame->LoadHarmonics();
1352
1353 if (ptcmgr->IsReady()) {
1354 SetbShowCurrent(bShow);
1355 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1356 } else {
1357 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1358 SetbShowCurrent(false);
1359 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1360 }
1361
1362 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1363 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1364
1365 // TODO
1366 // if( GetbShowCurrent() ) {
1367 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1368 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1369 // update
1370 // } else
1371 // FrameTCTimer.Stop();
1372}
1373
1374// TODO
1375extern bool g_bPreserveScaleOnX;
1376extern ChartDummy *pDummyChart;
1377extern int g_sticky_chart;
1378
1379void ChartCanvas::canvasRefreshGroupIndex(void) { SetGroupIndex(m_groupIndex); }
1380
1381void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1382 SetAlertString(_T(""));
1383
1384 int new_index = index;
1385 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1386
1387 bool bgroup_override = false;
1388 int old_group_index = new_index;
1389
1390 if (!CheckGroup(new_index)) {
1391 new_index = 0;
1392 bgroup_override = true;
1393 }
1394
1395 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1396 new_index = index;
1397
1398 // Get the currently displayed chart native scale, and the current ViewPort
1399 int current_chart_native_scale = GetCanvasChartNativeScale();
1400 ViewPort vp = GetVP();
1401
1402 m_groupIndex = new_index;
1403
1404 // Are there ENCs in this group
1405 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1406
1407 // Update the MUIBar for ENC availability
1408 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1409
1410 // Allow the chart database to pre-calculate the MBTile inclusion test
1411 // boolean...
1412 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1413
1414 // Invalidate the "sticky" chart on group change, since it might not be in
1415 // the new group
1416 g_sticky_chart = -1;
1417
1418 // We need a chartstack and quilt to figure out which chart to open in the
1419 // new group
1420 UpdateCanvasOnGroupChange();
1421
1422 int dbi_now = -1;
1423 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1424
1425 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1426
1427 // If a new reference chart is indicated, set a good scale for it.
1428 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1429 double best_scale = GetBestStartScale(dbi_hint, vp);
1430 SetVPScale(best_scale);
1431 }
1432
1433 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1434
1435 // Refresh the canvas, selecting the "best" chart,
1436 // applying the prior ViewPort exactly
1437 canvasChartsRefresh(dbi_hint);
1438
1439 UpdateCanvasControlBar();
1440
1441 if (!autoSwitch && bgroup_override) {
1442 // show a short timed message box
1443 wxString msg(_("Group \""));
1444
1445 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1446 msg += pGroup->m_group_name;
1447
1448 msg += _("\" is empty.");
1449
1450 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1451
1452 return;
1453 }
1454
1455 // Message box is deferred so that canvas refresh occurs properly before
1456 // dialog
1457 if (bgroup_override) {
1458 wxString msg(_("Group \""));
1459
1460 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1461 msg += pGroup->m_group_name;
1462
1463 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1464
1465 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1466 }
1467}
1468
1469bool ChartCanvas::CheckGroup(int igroup) {
1470 if (!ChartData) return true; // Not known yet...
1471
1472 if (igroup == 0) return true; // "all charts" is always OK
1473
1474 if (igroup < 0) // negative group is an error
1475 return false;
1476
1477 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1478
1479 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1480 // and auto-shift to group 0
1481 return false;
1482
1483 for (const auto &elem : pGroup->m_element_array) {
1484 for (unsigned int ic = 0;
1485 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1486 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1487 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1488
1489 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1490 }
1491 }
1492
1493 // If necessary, check for GSHHS
1494 for (const auto &elem : pGroup->m_element_array) {
1495 const wxString &element_root = elem.m_element_name;
1496 wxString test_string = _T("GSHH");
1497 if (element_root.Upper().Contains(test_string)) return true;
1498 }
1499
1500 return false;
1501}
1502
1503void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1504 if (!ChartData) return;
1505
1506 AbstractPlatform::ShowBusySpinner();
1507
1508 double old_scale = GetVPScale();
1509 InvalidateQuilt();
1510 SetQuiltRefChart(-1);
1511
1512 m_singleChart = NULL;
1513
1514 // delete m_pCurrentStack;
1515 // m_pCurrentStack = NULL;
1516
1517 // Build a new ChartStack
1518 if (!m_pCurrentStack) {
1519 m_pCurrentStack = new ChartStack;
1520 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1521 }
1522
1523 if (-1 != dbi_hint) {
1524 if (GetQuiltMode()) {
1525 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1526 SetQuiltRefChart(dbi_hint);
1527 } else {
1528 // Open the saved chart
1529 ChartBase *pTentative_Chart;
1530 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1531
1532 if (pTentative_Chart) {
1533 /* m_singleChart is always NULL here, (set above) should this go before
1534 * that? */
1535 if (m_singleChart) m_singleChart->Deactivate();
1536
1537 m_singleChart = pTentative_Chart;
1538 m_singleChart->Activate();
1539
1540 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1541 GetpCurrentStack(), m_singleChart->GetFullPath());
1542 }
1543 }
1544
1545 // refresh_Piano();
1546 } else {
1547 // Select reference chart from the stack, as though clicked by user
1548 // Make it the smallest scale chart on the stack
1549 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1550 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1551 SetQuiltRefChart(selected_index);
1552 }
1553
1554 // Validate the correct single chart, or set the quilt mode as appropriate
1555 SetupCanvasQuiltMode();
1556 if (!GetQuiltMode() && m_singleChart == 0) {
1557 // use a dummy like in DoChartUpdate
1558 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1559 m_singleChart = pDummyChart;
1560 SetVPScale(old_scale);
1561 }
1562
1563 ReloadVP();
1564
1565 UpdateCanvasControlBar();
1566 UpdateGPSCompassStatusBox(true);
1567
1568 SetCursor(wxCURSOR_ARROW);
1569
1570 AbstractPlatform::HideBusySpinner();
1571}
1572
1573bool ChartCanvas::DoCanvasUpdate(void) {
1574 double tLat, tLon; // Chart Stack location
1575 double vpLat, vpLon; // ViewPort location
1576 bool blong_jump = false;
1577 meters_to_shift = 0;
1578 dir_to_shift = 0;
1579
1580 bool bNewChart = false;
1581 bool bNewView = false;
1582 bool bCanvasChartAutoOpen = true; // debugging
1583
1584 bool bNewPiano = false;
1585 bool bOpenSpecified;
1586 ChartStack LastStack;
1587 ChartBase *pLast_Ch;
1588
1589 ChartStack WorkStack;
1590
1591 if (bDBUpdateInProgress) return false;
1592 if (!ChartData) return false;
1593
1594 if (ChartData->IsBusy()) return false;
1595
1596 // Startup case:
1597 // Quilting is enabled, but the last chart seen was not quiltable
1598 // In this case, drop to single chart mode, set persistence flag,
1599 // And open the specified chart
1600 // TODO implement this
1601 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1602 // if( GetQuiltMode() ) {
1603 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1604 // gFrame->ToggleQuiltMode();
1605 // m_bpersistent_quilt = true;
1606 // m_singleChart = NULL;
1607 // }
1608 // }
1609 // }
1610
1611 // If in auto-follow mode, use the current glat,glon to build chart
1612 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1613 // other means
1614
1615 if (m_bFollow) {
1616 tLat = gLat;
1617 tLon = gLon;
1618
1619 // Set the ViewPort center based on the OWNSHIP offset
1620 double dx = m_OSoffsetx;
1621 double dy = m_OSoffsety;
1622 double d_east = dx / GetVP().view_scale_ppm;
1623 double d_north = dy / GetVP().view_scale_ppm;
1624
1625 if (GetUpMode() == NORTH_UP_MODE) {
1626 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1627 } else {
1628 double offset_angle = atan2(d_north, d_east);
1629 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1630 double chart_angle = GetVPRotation();
1631 double target_angle = chart_angle + offset_angle;
1632 double d_east_mod = offset_distance * cos(target_angle);
1633 double d_north_mod = offset_distance * sin(target_angle);
1634 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1635 }
1636
1637 extern double gCog_gt;
1638
1639 // on lookahead mode, adjust the vp center point
1640 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1641 double cog_to_use = gCog;
1642 if (g_btenhertz &&
1643 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1644 cog_to_use = gCog_gt;
1645 blong_jump = true;
1646 }
1647 if (!g_btenhertz) cog_to_use = g_COGAvg;
1648
1649 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1650
1651 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1652 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1653
1654 double pixel_delta_tent =
1655 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1656
1657 double pixel_delta = 0;
1658
1659 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1660 // avoid jumping of the vp center point during slow maneuvering, or at
1661 // anchor....
1662 if (!std::isnan(gSog)) {
1663 if (gSog < 2.0)
1664 pixel_delta = 0.;
1665 else
1666 pixel_delta = pixel_delta_tent;
1667 }
1668
1669 meters_to_shift = 0;
1670 dir_to_shift = 0;
1671 if (!std::isnan(gCog)) {
1672 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1673 dir_to_shift = cog_to_use;
1674 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1675 &vpLon);
1676 } else {
1677 vpLat = gLat;
1678 vpLon = gLon;
1679 }
1680 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1681 m_OSoffsetx = 0; // center ownship on loss of GPS
1682 m_OSoffsety = 0;
1683 vpLat = gLat;
1684 vpLon = gLon;
1685 }
1686
1687 } else {
1688 tLat = m_vLat;
1689 tLon = m_vLon;
1690 vpLat = m_vLat;
1691 vpLon = m_vLon;
1692 }
1693
1694 if (GetQuiltMode()) {
1695 int current_db_index = -1;
1696 if (m_pCurrentStack)
1697 current_db_index =
1698 m_pCurrentStack
1699 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1700 // chart dbIndex
1701 else
1702 m_pCurrentStack = new ChartStack;
1703
1704 // This logic added to enable opening a chart when there is no
1705 // previous chart indication, either from inital startup, or from adding
1706 // new chart directory
1707 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1708 m_pCurrentStack) {
1709 if (m_pCurrentStack->nEntry) {
1710 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1711 1); // smallest scale
1712 SelectQuiltRefdbChart(new_dbIndex, true);
1713 m_bautofind = false;
1714 }
1715 }
1716
1717 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1718 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1719
1720 if (m_bFirstAuto) {
1721 // Allow the chart database to pre-calculate the MBTile inclusion test
1722 // boolean...
1723 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1724
1725 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1726 // physical pixels. On standard DPI displays where logical = physical
1727 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1728 // logical pixels, this ratio would be 0.5.
1729 double proposed_scale_onscreen =
1730 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1731
1732 int initial_db_index = m_restore_dbindex;
1733 if (initial_db_index < 0) {
1734 if (m_pCurrentStack->nEntry) {
1735 initial_db_index =
1736 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1737 } else
1738 m_bautofind = true; // initial_db_index = 0;
1739 }
1740
1741 if (m_pCurrentStack->nEntry) {
1742 int initial_type = ChartData->GetDBChartType(initial_db_index);
1743
1744 // Check to see if the target new chart is quiltable as a reference
1745 // chart
1746
1747 if (!IsChartQuiltableRef(initial_db_index)) {
1748 // If it is not quiltable, then walk the stack up looking for a
1749 // satisfactory chart i.e. one that is quiltable and of the same type
1750 // XXX if there's none?
1751 int stack_index = 0;
1752
1753 if (stack_index >= 0) {
1754 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1755 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1756 if (IsChartQuiltableRef(test_db_index) &&
1757 (initial_type ==
1758 ChartData->GetDBChartType(initial_db_index))) {
1759 initial_db_index = test_db_index;
1760 break;
1761 }
1762 stack_index++;
1763 }
1764 }
1765 }
1766
1767 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1768 if (pc) {
1769 SetQuiltRefChart(initial_db_index);
1770 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1771 }
1772 }
1773 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1774 // just GetVPScale(), so I'm not sure why it's necessary to define the
1775 // proposed_scale_onscreen variable.
1776 bNewView |= SetViewPoint(vpLat, vpLon,
1777 GetCanvasScaleFactor() / proposed_scale_onscreen,
1778 0, GetVPRotation());
1779 }
1780 // Measure rough jump distance if in bfollow mode
1781 // No good reason to do smooth pan for
1782 // jump distance more than one screen width at scale.
1783 bool super_jump = false;
1784 if (m_bFollow) {
1785 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1786 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1787 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1788 }
1789 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead) {
1790 int nstep = 5;
1791 if (blong_jump) nstep = 20;
1792 StartTimedMovementVP(vpLat, vpLon, nstep);
1793 } else {
1794 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1795 }
1796
1797 goto update_finish;
1798 }
1799
1800 // Single Chart Mode from here....
1801 pLast_Ch = m_singleChart;
1802 ChartTypeEnum new_open_type;
1803 ChartFamilyEnum new_open_family;
1804 if (pLast_Ch) {
1805 new_open_type = pLast_Ch->GetChartType();
1806 new_open_family = pLast_Ch->GetChartFamily();
1807 } else {
1808 new_open_type = CHART_TYPE_KAP;
1809 new_open_family = CHART_FAMILY_RASTER;
1810 }
1811
1812 bOpenSpecified = m_bFirstAuto;
1813
1814 // Make sure the target stack is valid
1815 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1816
1817 // Build a chart stack based on tLat, tLon
1818 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1819 m_groupIndex)) { // Bogus Lat, Lon?
1820 if (NULL == pDummyChart) {
1821 pDummyChart = new ChartDummy;
1822 bNewChart = true;
1823 }
1824
1825 if (m_singleChart)
1826 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1827
1828 m_singleChart = pDummyChart;
1829
1830 // If the current viewpoint is invalid, set the default scale to
1831 // something reasonable.
1832 double set_scale = GetVPScale();
1833 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1834
1835 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1836
1837 // If the chart stack has just changed, there is new status
1838 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1839 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1840 bNewPiano = true;
1841 bNewChart = true;
1842 }
1843 }
1844
1845 // Copy the new (by definition empty) stack into the target stack
1846 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1847
1848 goto update_finish;
1849 }
1850
1851 // Check to see if Chart Stack has changed
1852 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1853 // New chart stack, so...
1854 bNewPiano = true;
1855
1856 // Save a copy of the current stack
1857 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1858
1859 // Copy the new stack into the target stack
1860 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1861
1862 // Is Current Chart in new stack?
1863
1864 int tEntry = -1;
1865 if (NULL != m_singleChart) // this handles startup case
1866 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1867 m_singleChart->GetFullPath());
1868
1869 if (tEntry != -1) { // m_singleChart is in the new stack
1870 m_pCurrentStack->CurrentStackEntry = tEntry;
1871 bNewChart = false;
1872 }
1873
1874 else // m_singleChart is NOT in new stack
1875 { // So, need to open a new chart
1876 // Find the largest scale raster chart that opens OK
1877
1878 ChartBase *pProposed = NULL;
1879
1880 if (bCanvasChartAutoOpen) {
1881 bool search_direction =
1882 false; // default is to search from lowest to highest
1883 int start_index = 0;
1884
1885 // A special case: If panning at high scale, open largest scale
1886 // chart first
1887 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1888 (LastStack.nEntry == 0)) {
1889 search_direction = true;
1890 start_index = m_pCurrentStack->nEntry - 1;
1891 }
1892
1893 // Another special case, open specified index on program start
1894 if (bOpenSpecified) {
1895 search_direction = false;
1896 start_index = 0;
1897 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1898 start_index = 0;
1899
1900 new_open_type = CHART_TYPE_DONTCARE;
1901 }
1902
1903 pProposed = ChartData->OpenStackChartConditional(
1904 m_pCurrentStack, start_index, search_direction, new_open_type,
1905 new_open_family);
1906
1907 // Try to open other types/families of chart in some priority
1908 if (NULL == pProposed)
1909 pProposed = ChartData->OpenStackChartConditional(
1910 m_pCurrentStack, start_index, search_direction,
1911 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1912
1913 if (NULL == pProposed)
1914 pProposed = ChartData->OpenStackChartConditional(
1915 m_pCurrentStack, start_index, search_direction,
1916 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1917
1918 bNewChart = true;
1919
1920 } // bCanvasChartAutoOpen
1921
1922 else
1923 pProposed = NULL;
1924
1925 // If no go, then
1926 // Open a Dummy Chart
1927 if (NULL == pProposed) {
1928 if (NULL == pDummyChart) {
1929 pDummyChart = new ChartDummy;
1930 bNewChart = true;
1931 }
1932
1933 if (pLast_Ch)
1934 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1935
1936 pProposed = pDummyChart;
1937 }
1938
1939 // Arriving here, pProposed points to an opened chart, or NULL.
1940 if (m_singleChart) m_singleChart->Deactivate();
1941 m_singleChart = pProposed;
1942
1943 if (m_singleChart) {
1944 m_singleChart->Activate();
1945 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1946 m_pCurrentStack, m_singleChart->GetFullPath());
1947 }
1948 } // need new chart
1949
1950 // Arriving here, m_singleChart is opened and OK, or NULL
1951 if (NULL != m_singleChart) {
1952 // Setup the view using the current scale
1953 double set_scale = GetVPScale();
1954
1955 // If the current viewpoint is invalid, set the default scale to
1956 // something reasonable.
1957 if (!GetVP().IsValid())
1958 set_scale = 1. / 20000.;
1959 else { // otherwise, match scale if elected.
1960 double proposed_scale_onscreen;
1961
1962 if (m_bFollow) { // autoset the scale only if in autofollow
1963 double new_scale_ppm =
1964 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1965 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1966 } else
1967 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1968
1969 // This logic will bring a new chart onscreen at roughly twice the true
1970 // paper scale equivalent. Note that first chart opened on application
1971 // startup (bOpenSpecified = true) will open at the config saved scale
1972 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1973 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1974 double equivalent_vp_scale =
1975 GetCanvasScaleFactor() / proposed_scale_onscreen;
1976 double new_scale_ppm =
1977 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1978 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1979 }
1980
1981 if (m_bFollow) { // bounds-check the scale only if in autofollow
1982 proposed_scale_onscreen =
1983 wxMin(proposed_scale_onscreen,
1984 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1985 GetCanvasWidth()));
1986 proposed_scale_onscreen =
1987 wxMax(proposed_scale_onscreen,
1988 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1989 g_b_overzoom_x));
1990 }
1991
1992 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1993 }
1994
1995 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1996 m_singleChart->GetChartSkew() * PI / 180.,
1997 GetVPRotation());
1998 }
1999 } // new stack
2000
2001 else // No change in Chart Stack
2002 {
2003 if ((m_bFollow) && m_singleChart)
2004 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
2005 m_singleChart->GetChartSkew() * PI / 180.,
2006 GetVPRotation());
2007 }
2008
2009update_finish:
2010
2011 // TODO
2012 // if( bNewPiano ) UpdateControlBar();
2013
2014 m_bFirstAuto = false; // Auto open on program start
2015
2016 // If we need a Refresh(), do it here...
2017 // But don't duplicate a Refresh() done by SetViewPoint()
2018 if (bNewChart && !bNewView) Refresh(false);
2019
2020#ifdef ocpnUSE_GL
2021 // If a new chart, need to invalidate gl viewport for refresh
2022 // so the fbo gets flushed
2023 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
2024#endif
2025
2026 return bNewChart | bNewView;
2027}
2028
2029void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
2030 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
2031
2032 SetQuiltRefChart(db_index);
2033 if (ChartData) {
2034 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
2035 if (pc) {
2036 if (b_autoscale) {
2037 double best_scale_ppm = GetBestVPScale(pc);
2038 SetVPScale(best_scale_ppm);
2039 }
2040 } else
2041 SetQuiltRefChart(-1);
2042 } else
2043 SetQuiltRefChart(-1);
2044}
2045
2046void ChartCanvas::SelectQuiltRefChart(int selected_index) {
2047 std::vector<int> piano_chart_index_array =
2048 GetQuiltExtendedStackdbIndexArray();
2049 int current_db_index = piano_chart_index_array[selected_index];
2050
2051 SelectQuiltRefdbChart(current_db_index);
2052}
2053
2054double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
2055 if (pchart) {
2056 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
2057
2058 if ((g_bPreserveScaleOnX) ||
2059 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2060 double new_scale_ppm = GetVPScale();
2061 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2062 } else {
2063 // This logic will bring the new chart onscreen at roughly twice the true
2064 // paper scale equivalent.
2065 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2066 double equivalent_vp_scale =
2067 GetCanvasScaleFactor() / proposed_scale_onscreen;
2068 double new_scale_ppm =
2069 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2070 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2071 }
2072
2073 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2074 // set. Otherwise, we get severe performance problems on all platforms
2075
2076 double max_underzoom_multiplier = 2.0;
2077 if (GetVP().b_quilt) {
2078 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2079 pchart->GetChartType(),
2080 pchart->GetChartFamily());
2081 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2082 }
2083
2084 proposed_scale_onscreen = wxMin(
2085 proposed_scale_onscreen,
2086 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2087 max_underzoom_multiplier);
2088
2089 // And, do not allow excessive overzoom either
2090 proposed_scale_onscreen =
2091 wxMax(proposed_scale_onscreen,
2092 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2093
2094 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2095 } else
2096 return 1.0;
2097}
2098
2099void ChartCanvas::SetupCanvasQuiltMode(void) {
2100 if (GetQuiltMode()) // going to quilt mode
2101 {
2102 ChartData->LockCache();
2103
2104 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2105
2106 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2107
2108 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2109 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2110 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2111 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2112
2113 m_Piano->SetRoundedRectangles(true);
2114
2115 // Select the proper Ref chart
2116 int target_new_dbindex = -1;
2117 if (m_pCurrentStack) {
2118 target_new_dbindex =
2119 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2120
2121 if (-1 != target_new_dbindex) {
2122 if (!IsChartQuiltableRef(target_new_dbindex)) {
2123 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2124 int type = ChartData->GetDBChartType(target_new_dbindex);
2125
2126 // walk the stack up looking for a satisfactory chart
2127 int stack_index = m_pCurrentStack->CurrentStackEntry;
2128
2129 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2130 (stack_index >= 0)) {
2131 int proj_tent = ChartData->GetDBChartProj(
2132 m_pCurrentStack->GetDBIndex(stack_index));
2133 int type_tent = ChartData->GetDBChartType(
2134 m_pCurrentStack->GetDBIndex(stack_index));
2135
2136 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2137 if ((proj == proj_tent) && (type_tent == type)) {
2138 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2139 break;
2140 }
2141 }
2142 stack_index++;
2143 }
2144 }
2145 }
2146 }
2147
2148 if (IsChartQuiltableRef(target_new_dbindex))
2149 SelectQuiltRefdbChart(target_new_dbindex,
2150 false); // Try not to allow a scale change
2151 else
2152 SelectQuiltRefdbChart(-1, false);
2153
2154 m_singleChart = NULL; // Bye....
2155
2156 // Re-qualify the quilt reference chart selection
2157 AdjustQuiltRefChart();
2158
2159 // Restore projection type saved on last quilt mode toggle
2160 // TODO
2161 // if(g_sticky_projection != -1)
2162 // GetVP().SetProjectionType(g_sticky_projection);
2163 // else
2164 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2165 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2166
2167 } else // going to SC Mode
2168 {
2169 std::vector<int> empty_array;
2170 m_Piano->SetActiveKeyArray(empty_array);
2171 m_Piano->SetNoshowIndexArray(empty_array);
2172 m_Piano->SetEclipsedIndexArray(empty_array);
2173
2174 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2175 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2176 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2177 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2178 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2179
2180 m_Piano->SetRoundedRectangles(false);
2181 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2182 }
2183
2184 // When shifting from quilt to single chart mode, select the "best" single
2185 // chart to show
2186 if (!GetQuiltMode()) {
2187 if (ChartData && ChartData->IsValid()) {
2188 UnlockQuilt();
2189
2190 double tLat, tLon;
2191 if (m_bFollow == true) {
2192 tLat = gLat;
2193 tLon = gLon;
2194 } else {
2195 tLat = m_vLat;
2196 tLon = m_vLon;
2197 }
2198
2199 if (!m_singleChart) {
2200 // Build a temporary chart stack based on tLat, tLon
2201 ChartStack TempStack;
2202 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2203 m_groupIndex);
2204
2205 // Iterate over the quilt charts actually shown, looking for the
2206 // largest scale chart that will be in the new chartstack.... This
2207 // will (almost?) always be the reference chart....
2208
2209 ChartBase *Candidate_Chart = NULL;
2210 int cur_max_scale = (int)1e8;
2211
2212 ChartBase *pChart = GetFirstQuiltChart();
2213 while (pChart) {
2214 // Is this pChart in new stack?
2215 int tEntry =
2216 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2217 if (tEntry != -1) {
2218 if (pChart->GetNativeScale() < cur_max_scale) {
2219 Candidate_Chart = pChart;
2220 cur_max_scale = pChart->GetNativeScale();
2221 }
2222 }
2223 pChart = GetNextQuiltChart();
2224 }
2225
2226 m_singleChart = Candidate_Chart;
2227
2228 // If the quilt is empty, there is no "best" chart.
2229 // So, open the smallest scale chart in the current stack
2230 if (NULL == m_singleChart) {
2231 m_singleChart = ChartData->OpenStackChartConditional(
2232 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2233 CHART_FAMILY_DONTCARE);
2234 }
2235 }
2236
2237 // Invalidate all the charts in the quilt,
2238 // as any cached data may be region based and not have fullscreen coverage
2239 InvalidateAllQuiltPatchs();
2240
2241 if (m_singleChart) {
2242 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2243 std::vector<int> one_array;
2244 one_array.push_back(dbi);
2245 m_Piano->SetActiveKeyArray(one_array);
2246 }
2247
2248 if (m_singleChart) {
2249 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2250 }
2251 }
2252 // Invalidate the current stack so that it will be rebuilt on next tick
2253 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2254 }
2255}
2256
2257bool ChartCanvas::IsTempMenuBarEnabled() {
2258#ifdef __WXMSW__
2259 int major;
2260 wxGetOsVersion(&major);
2261 return (major >
2262 5); // For Windows, function is only available on Vista and above
2263#else
2264 return true;
2265#endif
2266}
2267
2268double ChartCanvas::GetCanvasRangeMeters() {
2269 int width, height;
2270 GetSize(&width, &height);
2271 int minDimension = wxMin(width, height);
2272
2273 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2274 range *= cos(GetVP().clat * PI / 180.);
2275 return range;
2276}
2277
2278void ChartCanvas::SetCanvasRangeMeters(double range) {
2279 int width, height;
2280 GetSize(&width, &height);
2281 int minDimension = wxMin(width, height);
2282
2283 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2284 SetVPScale(scale_ppm / 2);
2285}
2286
2287bool ChartCanvas::SetUserOwnship() {
2288 // Look for user defined ownship image
2289 // This may be found in the shared data location along with other user
2290 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2291 if (pWayPointMan && pWayPointMan->DoesIconExist(_T("ownship"))) {
2292 double factor_dusk = 0.5;
2293 double factor_night = 0.25;
2294
2295 wxBitmap *pbmp = pWayPointMan->GetIconBitmap(_T("ownship"));
2296 m_pos_image_user_day = new wxImage;
2297 *m_pos_image_user_day = pbmp->ConvertToImage();
2298 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2299
2300 int gimg_width = m_pos_image_user_day->GetWidth();
2301 int gimg_height = m_pos_image_user_day->GetHeight();
2302
2303 // Make dusk and night images
2304 m_pos_image_user_dusk = new wxImage;
2305 m_pos_image_user_night = new wxImage;
2306
2307 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2308 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2309
2310 for (int iy = 0; iy < gimg_height; iy++) {
2311 for (int ix = 0; ix < gimg_width; ix++) {
2312 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2313 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2314 m_pos_image_user_day->GetGreen(ix, iy),
2315 m_pos_image_user_day->GetBlue(ix, iy));
2316 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2317 hsv.value = hsv.value * factor_dusk;
2318 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2319 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2320 nrgb.blue);
2321
2322 hsv = wxImage::RGBtoHSV(rgb);
2323 hsv.value = hsv.value * factor_night;
2324 nrgb = wxImage::HSVtoRGB(hsv);
2325 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2326 nrgb.blue);
2327 }
2328 }
2329 }
2330
2331 // Make some alternate greyed out day/dusk/night images
2332 m_pos_image_user_grey_day = new wxImage;
2333 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2334
2335 m_pos_image_user_grey_dusk = new wxImage;
2336 m_pos_image_user_grey_night = new wxImage;
2337
2338 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2339 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2340
2341 for (int iy = 0; iy < gimg_height; iy++) {
2342 for (int ix = 0; ix < gimg_width; ix++) {
2343 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2344 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2345 m_pos_image_user_grey_day->GetGreen(ix, iy),
2346 m_pos_image_user_grey_day->GetBlue(ix, iy));
2347 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2348 hsv.value = hsv.value * factor_dusk;
2349 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2350 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2351 nrgb.blue);
2352
2353 hsv = wxImage::RGBtoHSV(rgb);
2354 hsv.value = hsv.value * factor_night;
2355 nrgb = wxImage::HSVtoRGB(hsv);
2356 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2357 nrgb.blue);
2358 }
2359 }
2360 }
2361
2362 // Make a yellow image for rendering under low accuracy chart conditions
2363 m_pos_image_user_yellow_day = new wxImage;
2364 m_pos_image_user_yellow_dusk = new wxImage;
2365 m_pos_image_user_yellow_night = new wxImage;
2366
2367 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2368 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2369 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2370
2371 for (int iy = 0; iy < gimg_height; iy++) {
2372 for (int ix = 0; ix < gimg_width; ix++) {
2373 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2374 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2375 m_pos_image_user_grey_day->GetGreen(ix, iy),
2376 m_pos_image_user_grey_day->GetBlue(ix, iy));
2377
2378 // Simply remove all "blue" from the greyscaled image...
2379 // so, what is not black becomes yellow.
2380 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2381 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2382 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2383
2384 hsv = wxImage::RGBtoHSV(rgb);
2385 hsv.value = hsv.value * factor_dusk;
2386 nrgb = wxImage::HSVtoRGB(hsv);
2387 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2388
2389 hsv = wxImage::RGBtoHSV(rgb);
2390 hsv.value = hsv.value * factor_night;
2391 nrgb = wxImage::HSVtoRGB(hsv);
2392 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2393 0);
2394 }
2395 }
2396 }
2397
2398 return true;
2399 } else
2400 return false;
2401}
2402
2404 m_display_size_mm = size;
2405
2406 // int sx, sy;
2407 // wxDisplaySize( &sx, &sy );
2408
2409 // Calculate logical pixels per mm for later reference.
2410 wxSize sd = g_Platform->getDisplaySize();
2411 double horizontal = sd.x;
2412 // Set DPI (Win) scale factor
2413 g_scaler = g_Platform->GetDisplayDIPMult(this);
2414
2415 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2416 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2417
2418 if (ps52plib) {
2419 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2420 ps52plib->SetPPMM(m_pix_per_mm);
2421 }
2422
2423 wxString msg;
2424 msg.Printf(
2425 _T("Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): ")
2426 _T("%d:%d "),
2427 m_display_size_mm, sd.x, sd.y);
2428 wxLogMessage(msg);
2429
2430 int ssx, ssy;
2431 ssx = g_monitor_info[g_current_monitor].width;
2432 ssy = g_monitor_info[g_current_monitor].height;
2433 msg.Printf(_T("monitor size: %d %d"), ssx, ssy);
2434 wxLogMessage(msg);
2435
2436 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2437}
2438#if 0
2439void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2440{
2441 wxString msg(event.m_string.c_str(), wxConvUTF8);
2442 // if cpus are removed between runs
2443 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2444 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2445 }
2446
2447 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2448 {
2449 compress_msg_array.RemoveAt(event.thread);
2450 compress_msg_array.Insert( msg, event.thread);
2451 }
2452 else
2453 compress_msg_array.Add(msg);
2454
2455
2456 wxString combined_msg;
2457 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2458 combined_msg += compress_msg_array[i];
2459 combined_msg += _T("\n");
2460 }
2461
2462 bool skip = false;
2463 pprog->Update(pprog_count, combined_msg, &skip );
2464 pprog->SetSize(pprog_size);
2465 if(skip)
2466 b_skipout = skip;
2467}
2468#endif
2469void ChartCanvas::InvalidateGL() {
2470 if (!m_glcc) return;
2471#ifdef ocpnUSE_GL
2472 if (g_bopengl) m_glcc->Invalidate();
2473#endif
2474 if (m_Compass) m_Compass->UpdateStatus(true);
2475}
2476
2477int ChartCanvas::GetCanvasChartNativeScale() {
2478 int ret = 1;
2479 if (!VPoint.b_quilt) {
2480 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2481 } else
2482 ret = (int)m_pQuilt->GetRefNativeScale();
2483
2484 return ret;
2485}
2486
2487ChartBase *ChartCanvas::GetChartAtCursor() {
2488 ChartBase *target_chart;
2489 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2490 target_chart = m_singleChart;
2491 else if (VPoint.b_quilt)
2492 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2493 else
2494 target_chart = NULL;
2495 return target_chart;
2496}
2497
2498ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2499 ChartBase *target_chart;
2500 if (VPoint.b_quilt)
2501 target_chart =
2502 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2503 else
2504 target_chart = NULL;
2505 return target_chart;
2506}
2507
2508int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2509 int new_dbIndex = -1;
2510 if (!VPoint.b_quilt) {
2511 if (m_pCurrentStack) {
2512 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2513 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2514 if (sc >= scale) {
2515 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2516 break;
2517 }
2518 }
2519 }
2520 } else {
2521 // Using the current quilt, select a useable reference chart
2522 // Said chart will be in the extended (possibly full-screen) stack,
2523 // And will have a scale equal to or just greater than the stipulated
2524 // value
2525 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2526 if (im > 0) {
2527 for (unsigned int is = 0; is < im; is++) {
2528 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2529 m_pQuilt->GetExtendedStackIndexArray()[is]);
2530 if ((m.Scale_ge(
2531 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2532 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2533 break;
2534 }
2535 }
2536 }
2537 }
2538
2539 return new_dbIndex;
2540}
2541
2542void ChartCanvas::EnablePaint(bool b_enable) {
2543 m_b_paint_enable = b_enable;
2544#ifdef ocpnUSE_GL
2545 if (m_glcc) m_glcc->EnablePaint(b_enable);
2546#endif
2547}
2548
2549bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2550
2551void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2552
2553std::vector<int> ChartCanvas::GetQuiltIndexArray(void) {
2554 return m_pQuilt->GetQuiltIndexArray();
2555 ;
2556}
2557
2558void ChartCanvas::SetQuiltMode(bool b_quilt) {
2559 VPoint.b_quilt = b_quilt;
2560 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2561}
2562
2563bool ChartCanvas::GetQuiltMode(void) { return VPoint.b_quilt; }
2564
2565int ChartCanvas::GetQuiltReferenceChartIndex(void) {
2566 return m_pQuilt->GetRefChartdbIndex();
2567}
2568
2569void ChartCanvas::InvalidateAllQuiltPatchs(void) {
2570 m_pQuilt->InvalidateAllQuiltPatchs();
2571}
2572
2573ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2574 return m_pQuilt->GetLargestScaleChart();
2575}
2576
2577ChartBase *ChartCanvas::GetFirstQuiltChart() {
2578 return m_pQuilt->GetFirstChart();
2579}
2580
2581ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2582
2583int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2584
2585void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2586 m_pQuilt->SetHiliteIndex(dbIndex);
2587}
2588
2589void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2590 m_pQuilt->SetHiliteIndexArray(hilite_array);
2591}
2592
2593void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2594 m_pQuilt->ClearHiliteIndexArray();
2595}
2596
2597std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2598 bool flag2) {
2599 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2600}
2601
2602int ChartCanvas::GetQuiltRefChartdbIndex(void) {
2603 return m_pQuilt->GetRefChartdbIndex();
2604}
2605
2606std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2607 return m_pQuilt->GetExtendedStackIndexArray();
2608}
2609
2610std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2611 return m_pQuilt->GetFullscreenIndexArray();
2612}
2613
2614std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2615 return m_pQuilt->GetEclipsedStackIndexArray();
2616}
2617
2618void ChartCanvas::InvalidateQuilt(void) { return m_pQuilt->Invalidate(); }
2619
2620double ChartCanvas::GetQuiltMaxErrorFactor() {
2621 return m_pQuilt->GetMaxErrorFactor();
2622}
2623
2624bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2625 return m_pQuilt->IsChartQuiltableRef(db_index);
2626}
2627
2628bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2629 double chartMaxScale =
2630 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2631 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2632}
2633
2634void ChartCanvas::StartMeasureRoute() {
2635 if (!m_routeState) { // no measure tool if currently creating route
2636 if (m_bMeasure_Active) {
2637 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2638 m_pMeasureRoute = NULL;
2639 }
2640
2641 m_bMeasure_Active = true;
2642 m_nMeasureState = 1;
2643 m_bDrawingRoute = false;
2644
2645 SetCursor(*pCursorPencil);
2646 Refresh();
2647 }
2648}
2649
2650void ChartCanvas::CancelMeasureRoute() {
2651 m_bMeasure_Active = false;
2652 m_nMeasureState = 0;
2653 m_bDrawingRoute = false;
2654
2655 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2656 m_pMeasureRoute = NULL;
2657
2658 SetCursor(*pCursorArrow);
2659}
2660
2661ViewPort &ChartCanvas::GetVP() { return VPoint; }
2662
2663void ChartCanvas::SetVP(ViewPort &vp) {
2664 VPoint = vp;
2665 VPoint.SetPixelScale(m_displayScale);
2666}
2667
2668// void ChartCanvas::SetFocus()
2669// {
2670// printf("set %d\n", m_canvasIndex);
2671// //wxWindow:SetFocus();
2672// }
2673
2674void ChartCanvas::TriggerDeferredFocus() {
2675 // #if defined(__WXGTK__) || defined(__WXOSX__)
2676
2677 m_deferredFocusTimer.Start(20, true);
2678
2679#if defined(__WXGTK__) || defined(__WXOSX__)
2680 gFrame->Raise();
2681#endif
2682
2683 // gFrame->Raise();
2684 // #else
2685 // SetFocus();
2686 // Refresh(true);
2687 // #endif
2688}
2689
2690void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2691 SetFocus();
2692 Refresh(true);
2693}
2694
2695void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2696 if (SendKeyEventToPlugins(event))
2697 return; // PlugIn did something, and does not want the canvas to do
2698 // anything else
2699
2700 int key_char = event.GetKeyCode();
2701 switch (key_char) {
2702 case '?':
2703 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2704 break;
2705 case '+':
2706 ZoomCanvas(g_plus_minus_zoom_factor, false);
2707 break;
2708 case '-':
2709 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2710 break;
2711 default:
2712 break;
2713 }
2714 if (g_benable_rotate) {
2715 switch (key_char) {
2716 case ']':
2717 RotateCanvas(1);
2718 Refresh();
2719 break;
2720
2721 case '[':
2722 RotateCanvas(-1);
2723 Refresh();
2724 break;
2725
2726 case '\\':
2727 DoRotateCanvas(0);
2728 break;
2729 }
2730 }
2731
2732 event.Skip();
2733}
2734
2735void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2736 if (SendKeyEventToPlugins(event))
2737 return; // PlugIn did something, and does not want the canvas to do
2738 // anything else
2739
2740 bool b_handled = false;
2741
2742 m_modkeys = event.GetModifiers();
2743
2744 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2745
2746#ifdef OCPN_ALT_MENUBAR
2747#ifndef __WXOSX__
2748 // If the permanent menubar is disabled, we show it temporarily when Alt is
2749 // pressed or when Alt + a letter is presssed (for the top-menu-level
2750 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2751 // some special cases.
2752 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2753 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2754 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2755 if (!g_bTempShowMenuBar) {
2756 g_bTempShowMenuBar = true;
2757 parent_frame->ApplyGlobalSettings(false);
2758 }
2759 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2760 event.Skip();
2761 return;
2762 }
2763 // If another key is pressed while Alt is down, do NOT toggle the menus when
2764 // Alt is released
2765 if (event.GetKeyCode() != WXK_ALT) {
2766 m_bMayToggleMenuBar = false;
2767 }
2768 }
2769#endif
2770#endif
2771
2772 // HOTKEYS
2773 switch (event.GetKeyCode()) {
2774 case WXK_TAB:
2775 // parent_frame->SwitchKBFocus( this );
2776 break;
2777
2778 case WXK_MENU:
2779 int x, y;
2780 event.GetPosition(&x, &y);
2781 m_FinishRouteOnKillFocus = false;
2782 CallPopupMenu(x, y);
2783 m_FinishRouteOnKillFocus = true;
2784 break;
2785
2786 case WXK_ALT:
2787 m_modkeys |= wxMOD_ALT;
2788 break;
2789
2790 case WXK_CONTROL:
2791 m_modkeys |= wxMOD_CONTROL;
2792 break;
2793
2794#ifdef __WXOSX__
2795 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2796 case WXK_RAW_CONTROL:
2797 m_modkeys |= wxMOD_RAW_CONTROL;
2798 break;
2799#endif
2800
2801 case WXK_LEFT:
2802 if (m_modkeys == wxMOD_CONTROL)
2803 parent_frame->DoStackDown(this);
2804 else if (g_bsmoothpanzoom) {
2805 StartTimedMovement();
2806 m_panx = -1;
2807 } else {
2808 PanCanvas(-panspeed, 0);
2809 }
2810 b_handled = true;
2811 break;
2812
2813 case WXK_UP:
2814 if (g_bsmoothpanzoom) {
2815 StartTimedMovement();
2816 m_pany = -1;
2817 } else
2818 PanCanvas(0, -panspeed);
2819 b_handled = true;
2820 break;
2821
2822 case WXK_RIGHT:
2823 if (m_modkeys == wxMOD_CONTROL)
2824 parent_frame->DoStackUp(this);
2825 else if (g_bsmoothpanzoom) {
2826 StartTimedMovement();
2827 m_panx = 1;
2828 } else
2829 PanCanvas(panspeed, 0);
2830 b_handled = true;
2831
2832 break;
2833
2834 case WXK_DOWN:
2835 if (g_bsmoothpanzoom) {
2836 StartTimedMovement();
2837 m_pany = 1;
2838 } else
2839 PanCanvas(0, panspeed);
2840 b_handled = true;
2841 break;
2842
2843 case WXK_F2:
2844 TogglebFollow();
2845 break;
2846
2847 case WXK_F3: {
2848 SetShowENCText(!GetShowENCText());
2849 Refresh(true);
2850 InvalidateGL();
2851 break;
2852 }
2853 case WXK_F4:
2854 if (!m_bMeasure_Active) {
2855 if (event.ShiftDown())
2856 m_bMeasure_DistCircle = true;
2857 else
2858 m_bMeasure_DistCircle = false;
2859
2860 StartMeasureRoute();
2861 } else {
2862 CancelMeasureRoute();
2863
2864 SetCursor(*pCursorArrow);
2865
2866 // SurfaceToolbar();
2867 InvalidateGL();
2868 Refresh(false);
2869 }
2870
2871 break;
2872
2873 case WXK_F5:
2874 parent_frame->ToggleColorScheme();
2875 gFrame->Raise();
2876 TriggerDeferredFocus();
2877 break;
2878
2879 case WXK_F6: {
2880 int mod = m_modkeys & wxMOD_SHIFT;
2881 if (mod != m_brightmod) {
2882 m_brightmod = mod;
2883 m_bbrightdir = !m_bbrightdir;
2884 }
2885
2886 if (!m_bbrightdir) {
2887 g_nbrightness -= 10;
2888 if (g_nbrightness <= MIN_BRIGHT) {
2889 g_nbrightness = MIN_BRIGHT;
2890 m_bbrightdir = true;
2891 }
2892 } else {
2893 g_nbrightness += 10;
2894 if (g_nbrightness >= MAX_BRIGHT) {
2895 g_nbrightness = MAX_BRIGHT;
2896 m_bbrightdir = false;
2897 }
2898 }
2899
2900 SetScreenBrightness(g_nbrightness);
2901 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2902
2903 SetFocus(); // just in case the external program steals it....
2904 gFrame->Raise(); // And reactivate the application main
2905
2906 break;
2907 }
2908
2909 case WXK_F7:
2910 parent_frame->DoStackDown(this);
2911 break;
2912
2913 case WXK_F8:
2914 parent_frame->DoStackUp(this);
2915 break;
2916
2917#ifndef __WXOSX__
2918 case WXK_F9: {
2919 ToggleCanvasQuiltMode();
2920 break;
2921 }
2922#endif
2923
2924 case WXK_F11:
2925 parent_frame->ToggleFullScreen();
2926 b_handled = true;
2927 break;
2928
2929 case WXK_F12: {
2930 if (m_modkeys == wxMOD_ALT) {
2931 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2932 } else {
2933 ToggleChartOutlines();
2934 }
2935 break;
2936 }
2937
2938 case WXK_PAUSE: // Drop MOB
2939 parent_frame->ActivateMOB();
2940 break;
2941
2942 // NUMERIC PAD
2943 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2944 case WXK_PAGEUP: {
2945 ZoomCanvas(g_plus_minus_zoom_factor, false);
2946 break;
2947 }
2948 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2949 case WXK_PAGEDOWN: {
2950 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2951 break;
2952 }
2953 case WXK_DELETE:
2954 case WXK_BACK:
2955 if (m_bMeasure_Active) {
2956 if (m_nMeasureState > 2) {
2957 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2958 m_pMeasureRoute->m_lastMousePointIndex =
2959 m_pMeasureRoute->GetnPoints();
2960 m_nMeasureState--;
2961 gFrame->RefreshAllCanvas();
2962 } else {
2963 CancelMeasureRoute();
2964 StartMeasureRoute();
2965 }
2966 }
2967 break;
2968 default:
2969 break;
2970 }
2971
2972 if (event.GetKeyCode() < 128) // ascii
2973 {
2974 int key_char = event.GetKeyCode();
2975
2976 // Handle both QWERTY and AZERTY keyboard separately for a few control
2977 // codes
2978 if (!g_b_assume_azerty) {
2979#ifdef __WXMAC__
2980 if (g_benable_rotate) {
2981 switch (key_char) {
2982 // On other platforms these are handled in OnKeyChar, which
2983 // (apparently) works better in some locales. On OS X it is better
2984 // to handle them here, since pressing Alt (which should change the
2985 // rotation speed) changes the key char and so prevents the keys
2986 // from working.
2987 case ']':
2988 RotateCanvas(1);
2989 b_handled = true;
2990 break;
2991
2992 case '[':
2993 RotateCanvas(-1);
2994 b_handled = true;
2995 break;
2996
2997 case '\\':
2998 DoRotateCanvas(0);
2999 b_handled = true;
3000 break;
3001 }
3002 }
3003#endif
3004 } else { // AZERTY
3005 switch (key_char) {
3006 case 43:
3007 ZoomCanvas(g_plus_minus_zoom_factor, false);
3008 break;
3009
3010 case 54: // '-' alpha/num pad
3011 // case 56: // '_' alpha/num pad
3012 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
3013 break;
3014 }
3015 }
3016
3017#ifdef __WXOSX__
3018 // Ctrl+Cmd+F toggles fullscreen on macOS
3019 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
3020 m_modkeys & wxMOD_RAW_CONTROL) {
3021 parent_frame->ToggleFullScreen();
3022 return;
3023 }
3024#endif
3025
3026 if (event.ControlDown()) key_char -= 64;
3027
3028 if (key_char >= '0' && key_char <= '9')
3029 SetGroupIndex(key_char - '0');
3030 else
3031
3032 switch (key_char) {
3033 case 'A':
3034 SetShowENCAnchor(!GetShowENCAnchor());
3035 ReloadVP();
3036
3037 break;
3038
3039 case 'C':
3040 parent_frame->ToggleColorScheme();
3041 break;
3042
3043 case 'D': {
3044 int x, y;
3045 event.GetPosition(&x, &y);
3046 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3047 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3048 // First find out what kind of chart is being used
3049 if (!pPopupDetailSlider) {
3050 if (VPoint.b_quilt) {
3051 if (m_pQuilt) {
3052 if (m_pQuilt->GetChartAtPix(
3053 VPoint,
3054 wxPoint(
3055 x, y))) // = null if no chart loaded for this point
3056 {
3057 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3058 ->GetChartType();
3059 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3060 ->GetChartFamily();
3061 }
3062 }
3063 } else {
3064 if (m_singleChart) {
3065 ChartType = m_singleChart->GetChartType();
3066 ChartFam = m_singleChart->GetChartFamily();
3067 }
3068 }
3069 // If a charttype is found show the popupslider
3070 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3071 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3072 pPopupDetailSlider = new PopUpDSlide(
3073 this, -1, ChartType, ChartFam,
3074 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3075 wxDefaultSize, wxSIMPLE_BORDER, _T(""));
3076 if (pPopupDetailSlider) pPopupDetailSlider->Show();
3077 }
3078 } else //( !pPopupDetailSlider ) close popupslider
3079 {
3080 if (pPopupDetailSlider) pPopupDetailSlider->Close();
3081 pPopupDetailSlider = NULL;
3082 }
3083 break;
3084 }
3085
3086 case 'E':
3087 m_nmea_log->Show();
3088 m_nmea_log->Raise();
3089 break;
3090
3091 case 'L':
3092 SetShowENCLights(!GetShowENCLights());
3093 ReloadVP();
3094
3095 break;
3096
3097 case 'M':
3098 if (event.ShiftDown())
3099 m_bMeasure_DistCircle = true;
3100 else
3101 m_bMeasure_DistCircle = false;
3102
3103 StartMeasureRoute();
3104 break;
3105
3106 case 'N':
3107 if (g_bInlandEcdis && ps52plib) {
3108 SetENCDisplayCategory((_DisCat)STANDARD);
3109 }
3110 break;
3111
3112 case 'O':
3113 ToggleChartOutlines();
3114 break;
3115
3116 case 'Q':
3117 ToggleCanvasQuiltMode();
3118 break;
3119
3120 case 'P':
3121 parent_frame->ToggleTestPause();
3122 break;
3123 case 'R':
3124 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3125 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3126 g_iNavAidRadarRingsNumberVisible = 1;
3127 else if (!g_bNavAidRadarRingsShown &&
3128 g_iNavAidRadarRingsNumberVisible == 1)
3129 g_iNavAidRadarRingsNumberVisible = 0;
3130 break;
3131 case 'S':
3132 SetShowENCDepth(!m_encShowDepth);
3133 ReloadVP();
3134 break;
3135
3136 case 'T':
3137 SetShowENCText(!GetShowENCText());
3138 ReloadVP();
3139 break;
3140
3141 case 'U':
3142 SetShowENCDataQual(!GetShowENCDataQual());
3143 ReloadVP();
3144 break;
3145
3146 case 'V':
3147 m_bShowNavobjects = !m_bShowNavobjects;
3148 Refresh(true);
3149 break;
3150
3151 case 'W': // W Toggle CPA alarm
3152 ToggleCPAWarn();
3153
3154 break;
3155
3156 case 1: // Ctrl A
3157 TogglebFollow();
3158
3159 break;
3160
3161 case 2: // Ctrl B
3162 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3163 break;
3164
3165 case 13: // Ctrl M // Drop Marker at cursor
3166 {
3167 if (event.ControlDown()) gFrame->DropMarker(false);
3168 break;
3169 }
3170
3171 case 14: // Ctrl N - Activate next waypoint in a route
3172 {
3173 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3174 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3175 if ((indexActive + 1) <= r->GetnPoints()) {
3176 g_pRouteMan->ActivateNextPoint(r, true);
3177 InvalidateGL();
3178 Refresh(false);
3179 }
3180 }
3181 break;
3182 }
3183
3184 case 15: // Ctrl O - Drop Marker at boat's position
3185 {
3186 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3187 break;
3188 }
3189
3190 case 32: // Special needs use space bar
3191 {
3192 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3193 break;
3194 }
3195
3196 case -32: // Ctrl Space // Drop MOB
3197 {
3198 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3199
3200 break;
3201 }
3202
3203 case -20: // Ctrl ,
3204 {
3205 parent_frame->DoSettings();
3206 break;
3207 }
3208 case 17: // Ctrl Q
3209 parent_frame->Close();
3210 return;
3211
3212 case 18: // Ctrl R
3213 StartRoute();
3214 return;
3215
3216 case 20: // Ctrl T
3217 if (NULL == pGoToPositionDialog) // There is one global instance of
3218 // the Go To Position Dialog
3219 pGoToPositionDialog = new GoToPositionDialog(this);
3220 pGoToPositionDialog->SetCanvas(this);
3221 pGoToPositionDialog->Show();
3222 break;
3223
3224 case 25: // Ctrl Y
3225 if (undo->AnythingToRedo()) {
3226 undo->RedoNextAction();
3227 InvalidateGL();
3228 Refresh(false);
3229 }
3230 break;
3231
3232 case 26:
3233 if (event.ShiftDown()) { // Shift-Ctrl-Z
3234 if (undo->AnythingToRedo()) {
3235 undo->RedoNextAction();
3236 InvalidateGL();
3237 Refresh(false);
3238 }
3239 } else { // Ctrl Z
3240 if (undo->AnythingToUndo()) {
3241 undo->UndoLastAction();
3242 InvalidateGL();
3243 Refresh(false);
3244 }
3245 }
3246 break;
3247
3248 case 27:
3249 // Generic break
3250 if (m_bMeasure_Active) {
3251 CancelMeasureRoute();
3252
3253 SetCursor(*pCursorArrow);
3254
3255 // SurfaceToolbar();
3256 gFrame->RefreshAllCanvas();
3257 }
3258
3259 if (m_routeState) // creating route?
3260 {
3261 FinishRoute();
3262 // SurfaceToolbar();
3263 InvalidateGL();
3264 Refresh(false);
3265 }
3266
3267 break;
3268
3269 case 7: // Ctrl G
3270 switch (gamma_state) {
3271 case (0):
3272 r_gamma_mult = 0;
3273 g_gamma_mult = 1;
3274 b_gamma_mult = 0;
3275 gamma_state = 1;
3276 break;
3277 case (1):
3278 r_gamma_mult = 1;
3279 g_gamma_mult = 0;
3280 b_gamma_mult = 0;
3281 gamma_state = 2;
3282 break;
3283 case (2):
3284 r_gamma_mult = 1;
3285 g_gamma_mult = 1;
3286 b_gamma_mult = 1;
3287 gamma_state = 0;
3288 break;
3289 }
3290 SetScreenBrightness(g_nbrightness);
3291
3292 break;
3293
3294 case 9: // Ctrl I
3295 if (event.ControlDown()) {
3296 m_bShowCompassWin = !m_bShowCompassWin;
3297 SetShowGPSCompassWindow(m_bShowCompassWin);
3298 Refresh(false);
3299 }
3300 break;
3301
3302 default:
3303 break;
3304
3305 } // switch
3306 }
3307
3308 // Allow OnKeyChar to catch the key events too.
3309 if (!b_handled) {
3310 event.Skip();
3311 }
3312}
3313
3314void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3315 if (SendKeyEventToPlugins(event))
3316 return; // PlugIn did something, and does not want the canvas to do
3317 // anything else
3318
3319 switch (event.GetKeyCode()) {
3320 case WXK_TAB:
3321 parent_frame->SwitchKBFocus(this);
3322 break;
3323
3324 case WXK_LEFT:
3325 case WXK_RIGHT:
3326 m_panx = 0;
3327 if (!m_pany) m_panspeed = 0;
3328 break;
3329
3330 case WXK_UP:
3331 case WXK_DOWN:
3332 m_pany = 0;
3333 if (!m_panx) m_panspeed = 0;
3334 break;
3335
3336 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3337 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3338 case WXK_PAGEUP:
3339 case WXK_PAGEDOWN:
3340 if (m_mustmove) DoMovement(m_mustmove);
3341
3342 m_zoom_factor = 1;
3343 break;
3344
3345 case WXK_ALT:
3346 m_modkeys &= ~wxMOD_ALT;
3347#ifdef OCPN_ALT_MENUBAR
3348#ifndef __WXOSX__
3349 // If the permanent menu bar is disabled, and we are not in the middle of
3350 // another key combo, then show the menu bar temporarily when Alt is
3351 // released (or hide it if already visible).
3352 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3353 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3354 parent_frame->ApplyGlobalSettings(false);
3355 }
3356 m_bMayToggleMenuBar = true;
3357#endif
3358#endif
3359 break;
3360
3361 case WXK_CONTROL:
3362 m_modkeys &= ~wxMOD_CONTROL;
3363 break;
3364 }
3365
3366 if (event.GetKeyCode() < 128) // ascii
3367 {
3368 int key_char = event.GetKeyCode();
3369
3370 // Handle both QWERTY and AZERTY keyboard separately for a few control
3371 // codes
3372 if (!g_b_assume_azerty) {
3373 switch (key_char) {
3374 case '+':
3375 case '=':
3376 case '-':
3377 case '_':
3378 case 54:
3379 case 56: // '_' alpha/num pad
3380 DoMovement(m_mustmove);
3381
3382 // m_zoom_factor = 1;
3383 break;
3384 case '[':
3385 case ']':
3386 DoMovement(m_mustmove);
3387 m_rotation_speed = 0;
3388 break;
3389 }
3390 } else {
3391 switch (key_char) {
3392 case 43:
3393 case 54: // '-' alpha/num pad
3394 case 56: // '_' alpha/num pad
3395 DoMovement(m_mustmove);
3396
3397 m_zoom_factor = 1;
3398 break;
3399 }
3400 }
3401 }
3402 event.Skip();
3403}
3404
3405void ChartCanvas::ToggleChartOutlines(void) {
3406 m_bShowOutlines = !m_bShowOutlines;
3407
3408 Refresh(false);
3409
3410#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3411 // needs a full refresh
3412 if (g_bopengl) InvalidateGL();
3413#endif
3414}
3415
3416void ChartCanvas::ToggleLookahead() {
3417 m_bLookAhead = !m_bLookAhead;
3418 m_OSoffsetx = 0; // center ownship
3419 m_OSoffsety = 0;
3420}
3421
3422void ChartCanvas::SetUpMode(int mode) {
3423 m_upMode = mode;
3424
3425 if (mode != NORTH_UP_MODE) {
3426 // Stuff the COGAvg table in case COGUp is selected
3427 double stuff = 0;
3428 if (!std::isnan(gCog)) stuff = gCog;
3429
3430 if (g_COGAvgSec > 0) {
3431 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3432 }
3433 g_COGAvg = stuff;
3434 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3435 } else {
3436 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3437 SetVPRotation(GetVPSkew());
3438 else
3439 SetVPRotation(0); /* reset to north up */
3440 }
3441
3442 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3443 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3444
3445 UpdateGPSCompassStatusBox(true);
3446 gFrame->DoChartUpdate();
3447}
3448
3449bool ChartCanvas::DoCanvasCOGSet(void) {
3450 if (GetUpMode() == NORTH_UP_MODE) return false;
3451 double cog_use = g_COGAvg;
3452 if (g_btenhertz) cog_use = gCog;
3453
3454 double rotation = 0;
3455 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3456 rotation = -gHdt * PI / 180.;
3457 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3458 rotation = -cog_use * PI / 180.;
3459
3460 SetVPRotation(rotation);
3461 return true;
3462}
3463
3464double easeOutCubic(double t) {
3465 // Starts quickly and slows down toward the end
3466 return 1.0 - pow(1.0 - t, 3.0);
3467}
3468
3469void ChartCanvas::StartChartDragInertia() {
3470 //
3471 // printf("\nStart ChartDragInertia\n");
3472 m_bChartDragging = false;
3473
3474 // Set some parameters
3475 m_chart_drag_inertia_time = 750; // msec
3476 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3477 m_last_elapsed = 0;
3478
3479 // Calculate ending drag velocity
3480 size_t n_vel = 10;
3481 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3482 int xacc = 0;
3483 int yacc = 0;
3484 double tacc = 0;
3485 size_t length = m_drag_vec_t.size();
3486 for (size_t i = 0; i < n_vel; i++) {
3487 xacc += m_drag_vec_x.at(length - 1 - i);
3488 yacc += m_drag_vec_y.at(length - 1 - i);
3489 tacc += m_drag_vec_t.at(length - 1 - i);
3490 // printf("%d %g\n", xacc, tacc);
3491 }
3492 m_chart_drag_velocity_x = xacc / tacc;
3493 m_chart_drag_velocity_y = yacc / tacc;
3494
3495 m_chart_drag_inertia_active = true;
3496
3497 // First callback as fast as possible.
3498 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3499
3500 // printf(" Drag parms %d %d %g\n", m_chart_drag_total_x,
3501 // m_chart_drag_total_y, m_chart_drag_total_time);
3502}
3503
3504void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3505 if (!m_chart_drag_inertia_active) return;
3506
3507 // Calculate time fraction from 0..1
3508 wxLongLong now = wxGetLocalTimeMillis();
3509 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3510 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3511 if (t > 1.0) t = 1.0;
3512 double e = 1.0 - easeOutCubic(t); // 0..1
3513
3514 double dx =
3515 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3516 double dy =
3517 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3518
3519 // double distance = pow((pow(dx, 2) + pow(dy, 2)), 0.5);
3520 // printf(" %5g %5g %5g %5g pix/sec\n", elapsed,
3521 // elapsed - m_last_elapsed, distance, distance * 1000 / elapsed);
3522
3523 m_last_elapsed = elapsed;
3524
3525 // Ensure that target destination lies on whole-pixel boundary
3526 // This allows the render engine to use a faster FBO copy method for drawing
3527 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3528 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3529 double inertia_lat, inertia_lon;
3530 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3531 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3532
3533 Refresh(false);
3534
3535 // Stop condition
3536 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3537 m_chart_drag_inertia_timer.Stop();
3538 m_chart_drag_inertia_active = false;
3539
3540 // Disable chart pan movement logic
3541 m_target_lat = GetVP().clat;
3542 m_target_lon = GetVP().clon;
3543 m_pan_drag.x = m_pan_drag.y = 0;
3544 m_panx = m_pany = 0;
3545
3546 } else {
3547 int target_redraw_interval = 40; // msec
3548 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3549 }
3550}
3551
3552void ChartCanvas::StopMovement() {
3553 m_panx = m_pany = 0;
3554 m_panspeed = 0;
3555 m_zoom_factor = 1;
3556 m_rotation_speed = 0;
3557 m_mustmove = 0;
3558#if 0
3559#if !defined(__WXGTK__) && !defined(__WXQT__)
3560 SetFocus();
3561 gFrame->Raise();
3562#endif
3563#endif
3564}
3565
3566/* instead of integrating in timer callbacks
3567 (which do not always get called fast enough)
3568 we can perform the integration of movement
3569 at each render frame based on the time change */
3570bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3571 // Start/restart the stop movement timer
3572 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3573
3574 if (!pMovementTimer->IsRunning()) {
3575 // printf("timer not running, starting\n");
3576 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3577 }
3578
3579 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3580 // already moving, gets called again because of key-repeat event
3581 return false;
3582 }
3583
3584 m_last_movement_time = wxDateTime::UNow();
3585
3586 return true;
3587}
3588void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3589 int nstep) {
3590 // Save the target
3591 m_target_lat = target_lat;
3592 m_target_lon = target_lon;
3593
3594 // Save the start point
3595 m_start_lat = GetVP().clat;
3596 m_start_lon = GetVP().clon;
3597
3598 m_VPMovementTimer.Start(1, true); // oneshot
3599 m_timed_move_vp_active = true;
3600 m_stvpc = 0;
3601 m_timedVP_step = nstep;
3602}
3603
3604void ChartCanvas::DoTimedMovementVP() {
3605 if (!m_timed_move_vp_active) return; // not active
3606 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3607 StopMovement();
3608 return;
3609 }
3610 // Stop condition
3611 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3612 double d2 =
3613 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3614 d2 = pow(d2, 0.5);
3615
3616 if (d2 < one_pix) {
3617 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3618 StopMovementVP();
3619 return;
3620 }
3621
3622 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3623 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3624 // StopMovementVP();
3625 // return;
3626 // }
3627
3628 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3629 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3630
3631 m_run_lat = new_lat;
3632 m_run_lon = new_lon;
3633
3634 // printf(" Timed\n");
3635 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3636}
3637
3638void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3639
3640void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3641
3642void ChartCanvas::StartTimedMovementTarget() {}
3643
3644void ChartCanvas::DoTimedMovementTarget() {}
3645
3646void ChartCanvas::StopMovementTarget() {}
3647
3648void ChartCanvas::DoTimedMovement() {
3649 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3650 !m_rotation_speed)
3651 return; /* not moving */
3652
3653 wxDateTime now = wxDateTime::UNow();
3654 long dt = 0;
3655 if (m_last_movement_time.IsValid())
3656 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3657
3658 m_last_movement_time = now;
3659
3660 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3661 dt = 500;
3662
3663 DoMovement(dt);
3664}
3665
3667 /* if we get here quickly assume 1ms so that some movement occurs */
3668 if (dt == 0) dt = 1;
3669
3670 m_mustmove -= dt;
3671 if (m_mustmove < 0) m_mustmove = 0;
3672
3673 if (m_pan_drag.x || m_pan_drag.y) {
3674 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3675 m_pan_drag.x = m_pan_drag.y = 0;
3676 }
3677
3678 if (m_panx || m_pany) {
3679 const double slowpan = .1, maxpan = 2;
3680 if (m_modkeys == wxMOD_ALT)
3681 m_panspeed = slowpan;
3682 else {
3683 m_panspeed += (double)dt / 500; /* apply acceleration */
3684 m_panspeed = wxMin(maxpan, m_panspeed);
3685 }
3686 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3687 }
3688
3689 if (m_zoom_factor != 1) {
3690 double alpha = 400, beta = 1.5;
3691 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3692
3693 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3694
3695 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3696
3697 // Try to hit the zoom target exactly.
3698 // if(m_wheelzoom_stop_oneshot > 0)
3699 {
3700 if (zoom_factor > 1) {
3701 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3702 zoom_factor = VPoint.chart_scale / m_zoom_target;
3703 }
3704
3705 else if (zoom_factor < 1) {
3706 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3707 zoom_factor = VPoint.chart_scale / m_zoom_target;
3708 }
3709 }
3710
3711 if (fabs(zoom_factor - 1) > 1e-4) {
3712 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3713 } else {
3714 StopMovement();
3715 }
3716
3717 if (m_wheelzoom_stop_oneshot > 0) {
3718 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3719 m_wheelzoom_stop_oneshot = 0;
3720 StopMovement();
3721 }
3722
3723 // Don't overshoot the zoom target.
3724 if (zoom_factor > 1) {
3725 if (VPoint.chart_scale <= m_zoom_target) {
3726 m_wheelzoom_stop_oneshot = 0;
3727 StopMovement();
3728 }
3729 } else if (zoom_factor < 1) {
3730 if (VPoint.chart_scale >= m_zoom_target) {
3731 m_wheelzoom_stop_oneshot = 0;
3732 StopMovement();
3733 }
3734 }
3735 }
3736 }
3737
3738 if (m_rotation_speed) { /* in degrees per second */
3739 double speed = m_rotation_speed;
3740 if (m_modkeys == wxMOD_ALT) speed /= 10;
3741 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3742 }
3743}
3744
3745void ChartCanvas::SetColorScheme(ColorScheme cs) {
3746 SetAlertString(_T(""));
3747
3748 // Setup ownship image pointers
3749 switch (cs) {
3750 case GLOBAL_COLOR_SCHEME_DAY:
3751 m_pos_image_red = &m_os_image_red_day;
3752 m_pos_image_grey = &m_os_image_grey_day;
3753 m_pos_image_yellow = &m_os_image_yellow_day;
3754 m_pos_image_user = m_pos_image_user_day;
3755 m_pos_image_user_grey = m_pos_image_user_grey_day;
3756 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3757 m_cTideBitmap = m_bmTideDay;
3758 m_cCurrentBitmap = m_bmCurrentDay;
3759
3760 break;
3761 case GLOBAL_COLOR_SCHEME_DUSK:
3762 m_pos_image_red = &m_os_image_red_dusk;
3763 m_pos_image_grey = &m_os_image_grey_dusk;
3764 m_pos_image_yellow = &m_os_image_yellow_dusk;
3765 m_pos_image_user = m_pos_image_user_dusk;
3766 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3767 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3768 m_cTideBitmap = m_bmTideDusk;
3769 m_cCurrentBitmap = m_bmCurrentDusk;
3770 break;
3771 case GLOBAL_COLOR_SCHEME_NIGHT:
3772 m_pos_image_red = &m_os_image_red_night;
3773 m_pos_image_grey = &m_os_image_grey_night;
3774 m_pos_image_yellow = &m_os_image_yellow_night;
3775 m_pos_image_user = m_pos_image_user_night;
3776 m_pos_image_user_grey = m_pos_image_user_grey_night;
3777 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3778 m_cTideBitmap = m_bmTideNight;
3779 m_cCurrentBitmap = m_bmCurrentNight;
3780 break;
3781 default:
3782 m_pos_image_red = &m_os_image_red_day;
3783 m_pos_image_grey = &m_os_image_grey_day;
3784 m_pos_image_yellow = &m_os_image_yellow_day;
3785 m_pos_image_user = m_pos_image_user_day;
3786 m_pos_image_user_grey = m_pos_image_user_grey_day;
3787 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3788 m_cTideBitmap = m_bmTideDay;
3789 m_cCurrentBitmap = m_bmCurrentDay;
3790 break;
3791 }
3792
3793 CreateDepthUnitEmbossMaps(cs);
3794 CreateOZEmbossMapData(cs);
3795
3796 // Set up fog effect base color
3797 m_fog_color = wxColor(
3798 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3799 float dim = 1.0;
3800 switch (cs) {
3801 case GLOBAL_COLOR_SCHEME_DUSK:
3802 dim = 0.5;
3803 break;
3804 case GLOBAL_COLOR_SCHEME_NIGHT:
3805 dim = 0.25;
3806 break;
3807 default:
3808 break;
3809 }
3810 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3811 m_fog_color.Blue() * dim);
3812
3813 // Really dark
3814#if 0
3815 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3816 SetBackgroundColour( wxColour(0,0,0) );
3817
3818 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3819 }
3820 else{
3821 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3822#ifndef __WXMAC__
3823 SetBackgroundColour( wxNullColour );
3824#endif
3825 }
3826#endif
3827
3828 // UpdateToolbarColorScheme(cs);
3829
3830 m_Piano->SetColorScheme(cs);
3831
3832 m_Compass->SetColorScheme(cs);
3833
3834 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3835
3836 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3837
3838 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3839 if (m_notification_button) {
3840 m_notification_button->SetColorScheme(cs);
3841 }
3842
3843#ifdef ocpnUSE_GL
3844 if (g_bopengl && m_glcc) {
3845 m_glcc->SetColorScheme(cs);
3846 g_glTextureManager->ClearAllRasterTextures();
3847 // m_glcc->FlushFBO();
3848 }
3849#endif
3850 SetbTCUpdate(true); // force re-render of tide/current locators
3851 m_brepaint_piano = true;
3852
3853 ReloadVP();
3854
3855 m_cs = cs;
3856}
3857
3858wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3859 wxImage img = Bitmap.ConvertToImage();
3860 int sx = img.GetWidth();
3861 int sy = img.GetHeight();
3862
3863 wxImage new_img(img);
3864
3865 for (int i = 0; i < sx; i++) {
3866 for (int j = 0; j < sy; j++) {
3867 if (!img.IsTransparent(i, j)) {
3868 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3869 (unsigned char)(img.GetGreen(i, j) * factor),
3870 (unsigned char)(img.GetBlue(i, j) * factor));
3871 }
3872 }
3873 }
3874
3875 wxBitmap ret = wxBitmap(new_img);
3876
3877 return ret;
3878}
3879
3880void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3881 int max) {
3882 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3883 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3884
3885 if (!m_pBrightPopup) {
3886 // Calculate size
3887 int x, y;
3888 GetTextExtent(_T("MAX"), &x, &y, NULL, NULL, pfont);
3889
3890 m_pBrightPopup = new TimedPopupWin(this, 3);
3891
3892 m_pBrightPopup->SetSize(x, y);
3893 m_pBrightPopup->Move(120, 120);
3894 }
3895
3896 int bmpsx = m_pBrightPopup->GetSize().x;
3897 int bmpsy = m_pBrightPopup->GetSize().y;
3898
3899 wxBitmap bmp(bmpsx, bmpsx);
3900 wxMemoryDC mdc(bmp);
3901
3902 mdc.SetTextForeground(GetGlobalColor(_T("GREEN4")));
3903 mdc.SetBackground(wxBrush(GetGlobalColor(_T("UINFD"))));
3904 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3905 mdc.SetBrush(wxBrush(GetGlobalColor(_T("UINFD"))));
3906 mdc.Clear();
3907
3908 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3909
3910 mdc.SetFont(*pfont);
3911 wxString val;
3912
3913 if (brightness == max)
3914 val = _T("MAX");
3915 else if (brightness == min)
3916 val = _T("MIN");
3917 else
3918 val.Printf(_T("%3d"), brightness);
3919
3920 mdc.DrawText(val, 0, 0);
3921
3922 mdc.SelectObject(wxNullBitmap);
3923
3924 m_pBrightPopup->SetBitmap(bmp);
3925 m_pBrightPopup->Show();
3926 m_pBrightPopup->Refresh();
3927}
3928
3929void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3930 m_b_rot_hidef = true;
3931 ReloadVP();
3932}
3933
3934void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3935 if (!g_bRollover) return;
3936
3937 bool b_need_refresh = false;
3938
3939 wxSize win_size = GetSize() * m_displayScale;
3940 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3941
3942 // Handle the AIS Rollover Window first
3943 bool showAISRollover = false;
3944 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3945 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3946 SelectItem *pFind = pSelectAIS->FindSelection(
3947 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3948 if (pFind) {
3949 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3950 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3951
3952 if (ptarget) {
3953 showAISRollover = true;
3954
3955 if (NULL == m_pAISRolloverWin) {
3956 m_pAISRolloverWin = new RolloverWin(this);
3957 m_pAISRolloverWin->IsActive(false);
3958 b_need_refresh = true;
3959 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3960 m_AISRollover_MMSI != FoundAIS_MMSI) {
3961 // Sometimes the mouse moves fast enough to get over a new AIS
3962 // target before the one-shot has fired to remove the old target.
3963 // Result: wrong target data is shown.
3964 // Detect this case,close the existing rollover ASAP, and restart
3965 // the timer.
3966 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3967 m_pAISRolloverWin->IsActive(false);
3968 m_AISRollover_MMSI = 0;
3969 Refresh();
3970 return;
3971 }
3972
3973 m_AISRollover_MMSI = FoundAIS_MMSI;
3974
3975 if (!m_pAISRolloverWin->IsActive()) {
3976 wxString s = ptarget->GetRolloverString();
3977 m_pAISRolloverWin->SetString(s);
3978
3979 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3980 AIS_ROLLOVER, win_size);
3981 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3982 m_pAISRolloverWin->IsActive(true);
3983 b_need_refresh = true;
3984 }
3985 }
3986 } else {
3987 m_AISRollover_MMSI = 0;
3988 showAISRollover = false;
3989 }
3990 }
3991
3992 // Maybe turn the rollover off
3993 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3994 m_pAISRolloverWin->IsActive(false);
3995 m_AISRollover_MMSI = 0;
3996 b_need_refresh = true;
3997 }
3998
3999 // Now the Route info rollover
4000 // Show the route segment info
4001 bool showRouteRollover = false;
4002
4003 if (NULL == m_pRolloverRouteSeg) {
4004 // Get a list of all selectable sgements, and search for the first
4005 // visible segment as the rollover target.
4006
4007 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4008 SelectableItemList SelList = pSelect->FindSelectionList(
4009 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
4010 wxSelectableItemListNode *node = SelList.GetFirst();
4011 while (node) {
4012 SelectItem *pFindSel = node->GetData();
4013
4014 Route *pr = (Route *)pFindSel->m_pData3; // candidate
4015
4016 if (pr && pr->IsVisible()) {
4017 m_pRolloverRouteSeg = pFindSel;
4018 showRouteRollover = true;
4019
4020 if (NULL == m_pRouteRolloverWin) {
4021 m_pRouteRolloverWin = new RolloverWin(this, 10);
4022 m_pRouteRolloverWin->IsActive(false);
4023 }
4024
4025 if (!m_pRouteRolloverWin->IsActive()) {
4026 wxString s;
4027 RoutePoint *segShow_point_a =
4028 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4029 RoutePoint *segShow_point_b =
4030 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4031
4032 double brg, dist;
4033 DistanceBearingMercator(
4034 segShow_point_b->m_lat, segShow_point_b->m_lon,
4035 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4036
4037 if (!pr->m_bIsInLayer)
4038 s.Append(_("Route") + _T(": "));
4039 else
4040 s.Append(_("Layer Route: "));
4041
4042 if (pr->m_RouteNameString.IsEmpty())
4043 s.Append(_("(unnamed)"));
4044 else
4045 s.Append(pr->m_RouteNameString);
4046
4047 s << _T("\n") << _("Total Length: ")
4048 << FormatDistanceAdaptive(pr->m_route_length) << _T("\n")
4049 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4050 << segShow_point_b->GetName() << _T("\n");
4051
4052 if (g_bShowTrue)
4053 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4054 (int)floor(brg + 0.5), 0x00B0);
4055 if (g_bShowMag) {
4056 double latAverage =
4057 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4058 double lonAverage =
4059 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4060 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4061
4062 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4063 (int)floor(varBrg + 0.5), 0x00B0);
4064 }
4065
4066 s << FormatDistanceAdaptive(dist);
4067
4068 // Compute and display cumulative distance from route start point to
4069 // current leg end point and RNG,TTG,ETA from ship to current leg end
4070 // point for active route
4071 double shiptoEndLeg = 0.;
4072 bool validActive = false;
4073 if (pr->IsActive() &&
4074 pr->pRoutePointList->GetFirst()->GetData()->m_bIsActive)
4075 validActive = true;
4076
4077 if (segShow_point_a != pr->pRoutePointList->GetFirst()->GetData()) {
4078 wxRoutePointListNode *node =
4079 (pr->pRoutePointList)->GetFirst()->GetNext();
4080 RoutePoint *prp;
4081 float dist_to_endleg = 0;
4082 wxString t;
4083
4084 while (node) {
4085 prp = node->GetData();
4086 if (validActive)
4087 shiptoEndLeg += prp->m_seg_len;
4088 else if (prp->m_bIsActive)
4089 validActive = true;
4090 dist_to_endleg += prp->m_seg_len;
4091 if (prp->IsSame(segShow_point_a)) break;
4092 node = node->GetNext();
4093 }
4094 s << _T(" (+") << FormatDistanceAdaptive(dist_to_endleg) << _T(")");
4095 }
4096 // write from ship to end selected leg point data if the route is
4097 // active
4098 if (validActive) {
4099 s << _T("\n") << _("From Ship To") << _T(" ")
4100 << segShow_point_b->GetName() << _T("\n");
4101 shiptoEndLeg +=
4102 g_pRouteMan
4103 ->GetCurrentRngToActivePoint(); // add distance from ship
4104 // to active point
4105 shiptoEndLeg +=
4106 segShow_point_b
4107 ->m_seg_len; // add the lenght of the selected leg
4108 s << FormatDistanceAdaptive(shiptoEndLeg);
4109 // ensure sog/cog are valid and vmg is positive to keep data
4110 // coherent
4111 double vmg = 0.;
4112 if (!std::isnan(gCog) && !std::isnan(gSog))
4113 vmg = gSog *
4114 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4115 PI / 180.);
4116 if (vmg > 0.) {
4117 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4118 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4119 s << _T(" - ")
4120 << wxString(ttg_sec > SECONDS_PER_DAY
4121 ? ttg_span.Format(_("%Dd %H:%M"))
4122 : ttg_span.Format(_("%H:%M")));
4123 wxDateTime dtnow, eta;
4124 eta = dtnow.SetToCurrent().Add(ttg_span);
4125 s << _T(" - ") << eta.Format(_T("%b")).Mid(0, 4)
4126 << eta.Format(_T(" %d %H:%M"));
4127 } else
4128 s << _T(" ---- ----");
4129 }
4130 m_pRouteRolloverWin->SetString(s);
4131
4132 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4133 LEG_ROLLOVER, win_size);
4134 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4135 m_pRouteRolloverWin->IsActive(true);
4136 b_need_refresh = true;
4137 showRouteRollover = true;
4138 break;
4139 }
4140 } else
4141 node = node->GetNext();
4142 }
4143 } else {
4144 // Is the cursor still in select radius, and not timed out?
4145 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4146 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4147 m_pRolloverRouteSeg))
4148 showRouteRollover = false;
4149 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4150 showRouteRollover = false;
4151 else
4152 showRouteRollover = true;
4153 }
4154
4155 // If currently creating a route, do not show this rollover window
4156 if (m_routeState) showRouteRollover = false;
4157
4158 // Similar for AIS target rollover window
4159 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4160 showRouteRollover = false;
4161
4162 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4163 !showRouteRollover) {
4164 m_pRouteRolloverWin->IsActive(false);
4165 m_pRolloverRouteSeg = NULL;
4166 m_pRouteRolloverWin->Destroy();
4167 m_pRouteRolloverWin = NULL;
4168 b_need_refresh = true;
4169 } else if (m_pRouteRolloverWin && showRouteRollover) {
4170 m_pRouteRolloverWin->IsActive(true);
4171 b_need_refresh = true;
4172 }
4173
4174 // Now the Track info rollover
4175 // Show the track segment info
4176 bool showTrackRollover = false;
4177
4178 if (NULL == m_pRolloverTrackSeg) {
4179 // Get a list of all selectable sgements, and search for the first
4180 // visible segment as the rollover target.
4181
4182 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4183 SelectableItemList SelList = pSelect->FindSelectionList(
4184 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4185 wxSelectableItemListNode *node = SelList.GetFirst();
4186 while (node) {
4187 SelectItem *pFindSel = node->GetData();
4188
4189 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4190
4191 if (pt && pt->IsVisible()) {
4192 m_pRolloverTrackSeg = pFindSel;
4193 showTrackRollover = true;
4194
4195 if (NULL == m_pTrackRolloverWin) {
4196 m_pTrackRolloverWin = new RolloverWin(this, 10);
4197 m_pTrackRolloverWin->IsActive(false);
4198 }
4199
4200 if (!m_pTrackRolloverWin->IsActive()) {
4201 wxString s;
4202 TrackPoint *segShow_point_a =
4203 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4204 TrackPoint *segShow_point_b =
4205 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4206
4207 double brg, dist;
4208 DistanceBearingMercator(
4209 segShow_point_b->m_lat, segShow_point_b->m_lon,
4210 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4211
4212 if (!pt->m_bIsInLayer)
4213 s.Append(_("Track") + _T(": "));
4214 else
4215 s.Append(_("Layer Track: "));
4216
4217 if (pt->GetName().IsEmpty())
4218 s.Append(_("(unnamed)"));
4219 else
4220 s.Append(pt->GetName());
4221 double tlenght = pt->Length();
4222 s << _T("\n") << _("Total Track: ")
4223 << FormatDistanceAdaptive(tlenght);
4224 if (pt->GetLastPoint()->GetTimeString() &&
4225 pt->GetPoint(0)->GetTimeString()) {
4226 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4227 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4228 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4229 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4230 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4231 s << wxString::Format(_T(" %.1f "), (float)(tlenght / htime))
4232 << getUsrSpeedUnit();
4233 s << wxString(htime > 24. ? ttime.Format(_T(" %Dd %H:%M"))
4234 : ttime.Format(_T(" %H:%M")));
4235 }
4236 }
4237
4238 if (g_bShowTrackPointTime &&
4239 strlen(segShow_point_b->GetTimeString())) {
4240 wxString stamp = segShow_point_b->GetTimeString();
4241 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4242 if (timestamp.IsValid()) {
4243 // Format track rollover timestamp to OCPN global TZ setting
4246 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4247 }
4248 s << _T("\n") << _("Segment Created: ") << stamp;
4249 }
4250
4251 s << _T("\n");
4252 if (g_bShowTrue)
4253 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4254 0x00B0);
4255
4256 if (g_bShowMag) {
4257 double latAverage =
4258 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4259 double lonAverage =
4260 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4261 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4262
4263 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4264 0x00B0);
4265 }
4266
4267 s << FormatDistanceAdaptive(dist);
4268
4269 if (segShow_point_a->GetTimeString() &&
4270 segShow_point_b->GetTimeString()) {
4271 wxDateTime apoint = segShow_point_a->GetCreateTime();
4272 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4273 if (apoint.IsValid() && bpoint.IsValid()) {
4274 double segmentSpeed = toUsrSpeed(
4275 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4276 s << wxString::Format(_T(" %.1f "), (float)segmentSpeed)
4277 << getUsrSpeedUnit();
4278 }
4279 }
4280
4281 m_pTrackRolloverWin->SetString(s);
4282
4283 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4284 LEG_ROLLOVER, win_size);
4285 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4286 m_pTrackRolloverWin->IsActive(true);
4287 b_need_refresh = true;
4288 showTrackRollover = true;
4289 break;
4290 }
4291 } else
4292 node = node->GetNext();
4293 }
4294 } else {
4295 // Is the cursor still in select radius, and not timed out?
4296 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4297 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4298 m_pRolloverTrackSeg))
4299 showTrackRollover = false;
4300 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4301 showTrackRollover = false;
4302 else
4303 showTrackRollover = true;
4304 }
4305
4306 // Similar for AIS target rollover window
4307 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4308 showTrackRollover = false;
4309
4310 // Similar for route rollover window
4311 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4312 showTrackRollover = false;
4313
4314 // TODO We onlt show tracks on primary canvas....
4315 // if(!IsPrimaryCanvas())
4316 // showTrackRollover = false;
4317
4318 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4319 !showTrackRollover) {
4320 m_pTrackRolloverWin->IsActive(false);
4321 m_pRolloverTrackSeg = NULL;
4322 m_pTrackRolloverWin->Destroy();
4323 m_pTrackRolloverWin = NULL;
4324 b_need_refresh = true;
4325 } else if (m_pTrackRolloverWin && showTrackRollover) {
4326 m_pTrackRolloverWin->IsActive(true);
4327 b_need_refresh = true;
4328 }
4329
4330 if (b_need_refresh) Refresh();
4331}
4332
4333void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4334 if ((GetShowENCLights() || m_bsectors_shown) &&
4335 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4336 extendedSectorLegs)) {
4337 if (!m_bsectors_shown) {
4338 ReloadVP(false);
4339 m_bsectors_shown = true;
4340 }
4341 } else {
4342 if (m_bsectors_shown) {
4343 ReloadVP(false);
4344 m_bsectors_shown = false;
4345 }
4346 }
4347
4348// This is here because GTK status window update is expensive..
4349// cairo using pango rebuilds the font every time so is very
4350// inefficient
4351// Anyway, only update the status bar when this timer expires
4352#if defined(__WXGTK__) || defined(__WXQT__)
4353 {
4354 // Check the absolute range of the cursor position
4355 // There could be a window wherein the chart geoereferencing is not
4356 // valid....
4357 double cursor_lat, cursor_lon;
4358 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4359
4360 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4361 while (cursor_lon < -180.) cursor_lon += 360.;
4362
4363 while (cursor_lon > 180.) cursor_lon -= 360.;
4364
4365 SetCursorStatus(cursor_lat, cursor_lon);
4366 }
4367 }
4368#endif
4369}
4370
4371void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4372 if (!parent_frame->m_pStatusBar) return;
4373
4374 wxString s1;
4375 s1 += _T(" ");
4376 s1 += toSDMM(1, cursor_lat);
4377 s1 += _T(" ");
4378 s1 += toSDMM(2, cursor_lon);
4379
4380 if (STAT_FIELD_CURSOR_LL >= 0)
4381 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4382
4383 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4384
4385 double brg, dist;
4386 wxString sm;
4387 wxString st;
4388 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4389 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4390 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4391
4392 wxString s = st + sm;
4393 s << FormatDistanceAdaptive(dist);
4394
4395 // CUSTOMIZATION - LIVE ETA OPTION
4396 // -------------------------------------------------------
4397 // Calculate an "live" ETA based on route starting from the current
4398 // position of the boat and goes to the cursor of the mouse.
4399 // In any case, an standard ETA will be calculated with a default speed
4400 // of the boat to give an estimation of the route (in particular if GPS
4401 // is off).
4402
4403 // Display only if option "live ETA" is selected in Settings > Display >
4404 // General.
4405 if (g_bShowLiveETA) {
4406 float realTimeETA;
4407 float boatSpeed;
4408 float boatSpeedDefault = g_defaultBoatSpeed;
4409
4410 // Calculate Estimate Time to Arrival (ETA) in minutes
4411 // Check before is value not closed to zero (it will make an very big
4412 // number...)
4413 if (!std::isnan(gSog)) {
4414 boatSpeed = gSog;
4415 if (boatSpeed < 0.5) {
4416 realTimeETA = 0;
4417 } else {
4418 realTimeETA = dist / boatSpeed * 60;
4419 }
4420 } else {
4421 realTimeETA = 0;
4422 }
4423
4424 // Add space after distance display
4425 s << " ";
4426 // Display ETA
4427 s << minutesToHoursDays(realTimeETA);
4428
4429 // In any case, display also an ETA with default speed at 6knts
4430
4431 s << " [@";
4432 s << wxString::Format(_T("%d"), (int)toUsrSpeed(boatSpeedDefault, -1));
4433 s << wxString::Format(_T("%s"), getUsrSpeedUnit(-1));
4434 s << " ";
4435 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4436 s << "]";
4437 }
4438 // END OF - LIVE ETA OPTION
4439
4440 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4441}
4442
4443// CUSTOMIZATION - FORMAT MINUTES
4444// -------------------------------------------------------
4445// New function to format minutes into a more readable format:
4446// * Hours + minutes, or
4447// * Days + hours.
4448wxString minutesToHoursDays(float timeInMinutes) {
4449 wxString s;
4450
4451 if (timeInMinutes == 0) {
4452 s << "--min";
4453 }
4454
4455 // Less than 60min, keep time in minutes
4456 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4457 s << wxString::Format(_T("%d"), (int)timeInMinutes);
4458 s << "min";
4459 }
4460
4461 // Between 1h and less than 24h, display time in hours, minutes
4462 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4463 int hours;
4464 int min;
4465 hours = (int)timeInMinutes / 60;
4466 min = (int)timeInMinutes % 60;
4467
4468 if (min == 0) {
4469 s << wxString::Format(_T("%d"), hours);
4470 s << "h";
4471 } else {
4472 s << wxString::Format(_T("%d"), hours);
4473 s << "h";
4474 s << wxString::Format(_T("%d"), min);
4475 s << "min";
4476 }
4477
4478 }
4479
4480 // More than 24h, display time in days, hours
4481 else if (timeInMinutes > 24 * 60) {
4482 int days;
4483 int hours;
4484 days = (int)(timeInMinutes / 60) / 24;
4485 hours = (int)(timeInMinutes / 60) % 24;
4486
4487 if (hours == 0) {
4488 s << wxString::Format(_T("%d"), days);
4489 s << "d";
4490 } else {
4491 s << wxString::Format(_T("%d"), days);
4492 s << "d";
4493 s << wxString::Format(_T("%d"), hours);
4494 s << "h";
4495 }
4496 }
4497
4498 return s;
4499}
4500
4501// END OF CUSTOMIZATION - FORMAT MINUTES
4502// Thanks open source code ;-)
4503// -------------------------------------------------------
4504
4505void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4506 double clat, clon;
4507 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4508 *lat = clat;
4509 *lon = clon;
4510}
4511
4512void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4513 wxPoint2DDouble *r) {
4514 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4515}
4516
4518 double rlon, wxPoint2DDouble *r) {
4519 // If the Current Chart is a raster chart, and the
4520 // requested lat/long is within the boundaries of the chart,
4521 // and the VP is not rotated,
4522 // then use the embedded BSB chart georeferencing algorithm
4523 // for greater accuracy
4524 // Additionally, use chart embedded georef if the projection is TMERC
4525 // i.e. NOT MERCATOR and NOT POLYCONIC
4526
4527 // If for some reason the chart rejects the request by returning an error,
4528 // then fall back to Viewport Projection estimate from canvas parameters
4529 if (!g_bopengl && m_singleChart &&
4530 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4531 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4532 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4533 (m_singleChart->GetChartProjectionType() !=
4534 PROJECTION_TRANSVERSE_MERCATOR) &&
4535 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4536 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4537 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4538 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4539 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4540 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4541 // Cur_BSB_Ch->GetCOVRTablenPoints
4542 // ( 0 ), rlon,
4543 // rlat );
4544 // bInside = true;
4545 // if ( bInside )
4546 if (Cur_BSB_Ch) {
4547 // This is a Raster chart....
4548 // If the VP is changing, the raster chart parameters may not yet be
4549 // setup So do that before accessing the chart's embedded
4550 // georeferencing
4551 Cur_BSB_Ch->SetVPRasterParms(vp);
4552 double rpixxd, rpixyd;
4553 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4554 r->m_x = rpixxd;
4555 r->m_y = rpixyd;
4556 return;
4557 }
4558 }
4559 }
4560
4561 // if needed, use the VPoint scaling estimator,
4562 *r = vp.GetDoublePixFromLL(rlat, rlon);
4563}
4564
4565// This routine might be deleted and all of the rendering improved
4566// to have floating point accuracy
4567bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4568 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4569}
4570
4571bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4572 wxPoint *r) {
4573 wxPoint2DDouble p;
4574 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4575
4576 // some projections give nan values when invisible values (other side of
4577 // world) are requested we should stop using integer coordinates or return
4578 // false here (and test it everywhere)
4579 if (std::isnan(p.m_x)) {
4580 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4581 return false;
4582 }
4583
4584 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4585 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4586 else
4587 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4588
4589 return true;
4590}
4591
4592void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4593 double &lon) {
4594 // If the Current Chart is a raster chart, and the
4595 // requested x,y is within the boundaries of the chart,
4596 // and the VP is not rotated,
4597 // then use the embedded BSB chart georeferencing algorithm
4598 // for greater accuracy
4599 // Additionally, use chart embedded georef if the projection is TMERC
4600 // i.e. NOT MERCATOR and NOT POLYCONIC
4601
4602 // If for some reason the chart rejects the request by returning an error,
4603 // then fall back to Viewport Projection estimate from canvas parameters
4604 bool bUseVP = true;
4605
4606 if (!g_bopengl && m_singleChart &&
4607 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4608 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4609 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4610 (m_singleChart->GetChartProjectionType() !=
4611 PROJECTION_TRANSVERSE_MERCATOR) &&
4612 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4613 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4614 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4615 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4616
4617 // TODO maybe need iterative process to validate bInside
4618 // first pass is mercator, then check chart boundaries
4619
4620 if (Cur_BSB_Ch) {
4621 // This is a Raster chart....
4622 // If the VP is changing, the raster chart parameters may not yet be
4623 // setup So do that before accessing the chart's embedded
4624 // georeferencing
4625 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4626
4627 double slat, slon;
4628 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4629 lat = slat;
4630
4631 if (slon < -180.)
4632 slon += 360.;
4633 else if (slon > 180.)
4634 slon -= 360.;
4635
4636 lon = slon;
4637 bUseVP = false;
4638 }
4639 }
4640 }
4641
4642 // if needed, use the VPoint scaling estimator
4643 if (bUseVP) {
4644 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4645 }
4646}
4647
4649 StopMovement();
4650 DoZoomCanvas(factor, false);
4651 extendedSectorLegs.clear();
4652}
4653
4654void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4655 bool stoptimer) {
4656 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4657
4658 if (g_bsmoothpanzoom) {
4659 if (StartTimedMovement(stoptimer)) {
4660 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4661 m_zoom_factor = factor;
4662 }
4663
4664 m_zoom_target = VPoint.chart_scale / factor;
4665 } else {
4666 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4667
4668 DoZoomCanvas(factor, can_zoom_to_cursor);
4669 }
4670
4671 extendedSectorLegs.clear();
4672}
4673
4674void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4675 // possible on startup
4676 if (!ChartData) return;
4677 if (!m_pCurrentStack) return;
4678
4679 /* TODO: queue the quilted loading code to a background thread
4680 so yield is never called from here, and also rendering is not delayed */
4681
4682 // Cannot allow Yield() re-entrancy here
4683 if (m_bzooming) return;
4684 m_bzooming = true;
4685
4686 double old_ppm = GetVP().view_scale_ppm;
4687
4688 // Capture current cursor position for zoom to cursor
4689 double zlat = m_cursor_lat;
4690 double zlon = m_cursor_lon;
4691
4692 double proposed_scale_onscreen =
4693 GetVP().chart_scale /
4694 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4695 bool b_do_zoom = false;
4696
4697 if (factor > 1) {
4698 b_do_zoom = true;
4699
4700 // double zoom_factor = factor;
4701
4702 ChartBase *pc = NULL;
4703
4704 if (!VPoint.b_quilt) {
4705 pc = m_singleChart;
4706 } else {
4707 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4708 if (new_db_index >= 0)
4709 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4710 else { // for whatever reason, no reference chart is known
4711 // Choose the smallest scale chart on the current stack
4712 // and then adjust for scale range
4713 int current_ref_stack_index = -1;
4714 if (m_pCurrentStack->nEntry) {
4715 int trial_index =
4716 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4717 m_pQuilt->SetReferenceChart(trial_index);
4718 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4719 if (new_db_index >= 0)
4720 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4721 }
4722 }
4723
4724 if (m_pCurrentStack)
4725 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4726 new_db_index); // highlite the correct bar entry
4727 }
4728
4729 if (pc) {
4730 // double target_scale_ppm = GetVPScale() * zoom_factor;
4731 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4732 // target_scale_ppm;
4733
4734 // Query the chart to determine the appropriate zoom range
4735 double min_allowed_scale =
4736 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4737
4738 if (proposed_scale_onscreen < min_allowed_scale) {
4739 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4740 m_zoom_factor = 1; /* stop zooming */
4741 b_do_zoom = false;
4742 } else
4743 proposed_scale_onscreen = min_allowed_scale;
4744 }
4745
4746 } else {
4747 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4748 }
4749
4750 } else if (factor < 1) {
4751 b_do_zoom = true;
4752
4753 ChartBase *pc = NULL;
4754
4755 bool b_smallest = false;
4756
4757 if (!VPoint.b_quilt) { // not quilted
4758 pc = m_singleChart;
4759
4760 if (pc) {
4761 // If m_singleChart is not on the screen, unbound the zoomout
4762 LLBBox viewbox = VPoint.GetBBox();
4763 // BoundingBox chart_box;
4764 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4765 double max_allowed_scale;
4766
4767 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4768
4769 // We can allow essentially unbounded zoomout in single chart mode
4770 // if( ChartData->GetDBBoundingBox( current_index,
4771 // &chart_box ) &&
4772 // !viewbox.IntersectOut( chart_box ) )
4773 // // Clamp the minimum scale zoom-out to the value
4774 // specified by the chart max_allowed_scale =
4775 // wxMin(max_allowed_scale, 4.0 *
4776 // pc->GetNormalScaleMax(
4777 // GetCanvasScaleFactor(),
4778 // GetCanvasWidth() ) );
4779 if (proposed_scale_onscreen > max_allowed_scale) {
4780 m_zoom_factor = 1; /* stop zooming */
4781 proposed_scale_onscreen = max_allowed_scale;
4782 }
4783 }
4784
4785 } else {
4786 int new_db_index = m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4787 if (new_db_index >= 0)
4788 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4789
4790 if (m_pCurrentStack)
4791 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4792 new_db_index); // highlite the correct bar entry
4793
4794 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4795
4796 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4797 proposed_scale_onscreen =
4798 wxMin(proposed_scale_onscreen,
4799 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4800 }
4801
4802 // set a minimum scale
4803 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4804 m_absolute_min_scale_ppm)
4805 b_do_zoom = false;
4806 }
4807
4808 double new_scale =
4809 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4810
4811 if (b_do_zoom) {
4812 // Disable ZTC if lookahead is ON, and currently b_follow is active
4813 bool b_allow_ztc = true;
4814 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4815 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4816 if (m_bLookAhead) {
4817 double brg, distance;
4818 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4819 &distance);
4820 dir_to_shift = brg;
4821 meters_to_shift = distance * 1852;
4822 }
4823 // Arrange to combine the zoom and pan into one operation for smoother
4824 // appearance
4825 SetVPScale(new_scale, false); // adjust, but deferred refresh
4826 wxPoint r;
4827 GetCanvasPointPix(zlat, zlon, &r);
4828 // this will emit the Refresh()
4829 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4830 } else {
4831 SetVPScale(new_scale);
4832 if (m_bFollow) DoCanvasUpdate();
4833 }
4834 }
4835
4836 m_bzooming = false;
4837}
4838int rot;
4839void ChartCanvas::RotateCanvas(double dir) {
4840 // SetUpMode(NORTH_UP_MODE);
4841
4842 if (g_bsmoothpanzoom) {
4843 if (StartTimedMovement()) {
4844 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4845 m_rotation_speed = dir * 60;
4846 }
4847 } else {
4848 double speed = dir * 10;
4849 if (m_modkeys == wxMOD_ALT) speed /= 20;
4850 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4851 }
4852}
4853
4854void ChartCanvas::DoRotateCanvas(double rotation) {
4855 while (rotation < 0) rotation += 2 * PI;
4856 while (rotation > 2 * PI) rotation -= 2 * PI;
4857
4858 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4859
4860 SetVPRotation(rotation);
4861 parent_frame->UpdateRotationState(VPoint.rotation);
4862}
4863
4864void ChartCanvas::DoTiltCanvas(double tilt) {
4865 while (tilt < 0) tilt = 0;
4866 while (tilt > .95) tilt = .95;
4867
4868 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4869
4870 VPoint.tilt = tilt;
4871 Refresh(false);
4872}
4873
4874void ChartCanvas::TogglebFollow(void) {
4875 if (!m_bFollow)
4876 SetbFollow();
4877 else
4878 ClearbFollow();
4879}
4880
4881void ChartCanvas::ClearbFollow(void) {
4882 m_bFollow = false; // update the follow flag
4883
4884 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4885
4886 UpdateFollowButtonState();
4887
4888 DoCanvasUpdate();
4889 ReloadVP();
4890 parent_frame->SetChartUpdatePeriod();
4891}
4892
4893void ChartCanvas::SetbFollow(void) {
4894 // Is the OWNSHIP on-screen?
4895 // If not, then reset the OWNSHIP offset to 0 (center screen)
4896 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4897 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4898 m_OSoffsetx = 0;
4899 m_OSoffsety = 0;
4900 }
4901
4902 // Apply the present b_follow offset values to ship position
4903 wxPoint2DDouble p;
4904 GetDoubleCanvasPointPix(gLat, gLon, &p);
4905 p.m_x += m_OSoffsetx;
4906 p.m_y -= m_OSoffsety;
4907
4908 // compute the target center screen lat/lon
4909 double dlat, dlon;
4910 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4911
4912 JumpToPosition(dlat, dlon, GetVPScale());
4913 m_bFollow = true;
4914
4915 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4916 UpdateFollowButtonState();
4917
4918 if (!g_bSmoothRecenter) {
4919 DoCanvasUpdate();
4920 ReloadVP();
4921 }
4922 parent_frame->SetChartUpdatePeriod();
4923}
4924
4925void ChartCanvas::UpdateFollowButtonState(void) {
4926 if (m_muiBar) {
4927 if (!m_bFollow)
4928 m_muiBar->SetFollowButtonState(0);
4929 else {
4930 if (m_bLookAhead)
4931 m_muiBar->SetFollowButtonState(2);
4932 else
4933 m_muiBar->SetFollowButtonState(1);
4934 }
4935 }
4936
4937#ifdef __ANDROID__
4938 if (!m_bFollow)
4939 androidSetFollowTool(0);
4940 else {
4941 if (m_bLookAhead)
4942 androidSetFollowTool(2);
4943 else
4944 androidSetFollowTool(1);
4945 }
4946#endif
4947}
4948
4949void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4950 if (g_bSmoothRecenter && !m_routeState) {
4951 if (StartSmoothJump(lat, lon, scale_ppm))
4952 return;
4953 else {
4954 // move closer to the target destination, and try again
4955 double gcDist, gcBearingEnd;
4956 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4957 &gcBearingEnd);
4958 gcBearingEnd += 180;
4959 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4960 GetCanvasWidth() / GetVPScale(); // meters
4961 double lon_offset =
4962 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4963 double new_lat = lat + (lat_offset / (1852 * 60));
4964 double new_lon = lon + (lon_offset / (1852 * 60));
4965 SetViewPoint(new_lat, new_lon);
4966 ReloadVP();
4967 StartSmoothJump(lat, lon, scale_ppm);
4968 return;
4969 }
4970 }
4971
4972 if (lon > 180.0) lon -= 360.0;
4973 m_vLat = lat;
4974 m_vLon = lon;
4975 StopMovement();
4976 m_bFollow = false;
4977
4978 if (!GetQuiltMode()) {
4979 double skew = 0;
4980 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4981 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4982 } else {
4983 if (scale_ppm != GetVPScale()) {
4984 // XXX should be done in SetViewPoint
4985 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4986 AdjustQuiltRefChart();
4987 }
4988 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4989 }
4990
4991 ReloadVP();
4992
4993 UpdateFollowButtonState();
4994
4995 // TODO
4996 // if( g_pi_manager ) {
4997 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4998 // }
4999}
5000
5001bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
5002 // Check distance to jump, in pixels at current chart scale
5003 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5004 // width.
5005 double gcDist;
5006 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5007 double distance_pixels = gcDist * GetVPScale();
5008 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5009 // Jump is too far, try again
5010 return false;
5011 }
5012
5013 // Save where we're coming from
5014 m_startLat = m_vLat;
5015 m_startLon = m_vLon;
5016 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5017
5018 // Save where we want to end up
5019 m_endLat = lat;
5020 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5021 m_endScale = scale_ppm;
5022
5023 // Setup timing
5024 m_animationDuration = 600; // ms
5025 m_animationStart = wxGetLocalTimeMillis();
5026
5027 // Stop any previous movement, ensure no conflicts
5028 StopMovement();
5029 m_bFollow = false;
5030
5031 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5032 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5033 m_animationActive = true;
5034
5035 return true;
5036}
5037
5038void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5039 // Calculate time fraction from 0..1
5040 wxLongLong now = wxGetLocalTimeMillis();
5041 double elapsed = (now - m_animationStart).ToDouble();
5042 double t = elapsed / m_animationDuration.ToDouble();
5043 if (t > 1.0) t = 1.0;
5044
5045 // Ease function for smoother movement
5046 double e = easeOutCubic(t);
5047
5048 // Interpolate lat/lon/scale
5049 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5050 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5051 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5052
5053 // Update viewpoint
5054 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5055 // portion)
5056 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5057 ReloadVP();
5058
5059 // If we reached the end, stop the timer and finalize
5060 if (t >= 1.0) {
5061 m_easeTimer.Stop();
5062 m_animationActive = false;
5063 UpdateFollowButtonState();
5064 ZoomCanvasSimple(1.0001);
5065 DoCanvasUpdate();
5066 ReloadVP();
5067 }
5068}
5069
5070bool ChartCanvas::PanCanvas(double dx, double dy) {
5071 if (!ChartData) return false;
5072
5073 if (g_btouch) {
5074 // Stop bfollow state, without a refresh
5075 m_bFollow = false; // update the follow flag
5076 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
5077 UpdateFollowButtonState();
5078 // Clear the bfollow offset
5079 m_OSoffsetx = 0;
5080 m_OSoffsety = 0;
5081 }
5082
5083 extendedSectorLegs.clear();
5084
5085 // double clat = VPoint.clat, clon = VPoint.clon;
5086 double dlat, dlon;
5087 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5088
5089 int iters = 0;
5090 for (;;) {
5091 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5092
5093 if (iters++ > 5) return false;
5094 if (!std::isnan(dlat)) break;
5095
5096 dx *= .5, dy *= .5;
5097 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5098 }
5099
5100 // avoid overshooting the poles
5101 if (dlat > 90)
5102 dlat = 90;
5103 else if (dlat < -90)
5104 dlat = -90;
5105
5106 if (dlon > 360.) dlon -= 360.;
5107 if (dlon < -360.) dlon += 360.;
5108
5109 // This should not really be necessary, but round-trip georef on some
5110 // charts is not perfect, So we can get creep on repeated unidimensional
5111 // pans, and corrupt chart cacheing.......
5112
5113 // But this only works on north-up projections
5114 // TODO: can we remove this now?
5115 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5116 // .001 ) ) {
5117 //
5118 // if( dx == 0 ) dlon = clon;
5119 // if( dy == 0 ) dlat = clat;
5120 // }
5121
5122 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5123
5124 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5125
5126 if (VPoint.b_quilt) {
5127 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5128 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5129 // Tweak the scale slightly for a new ref chart
5130 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5131 if (pc) {
5132 double tweak_scale_ppm =
5133 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5134 SetVPScale(tweak_scale_ppm);
5135 }
5136 }
5137
5138 if (new_ref_dbIndex == -1) {
5139#pragma GCC diagnostic push
5140#pragma GCC diagnostic ignored "-Warray-bounds"
5141 // The compiler sees a -1 index being used. Does not happen, though.
5142
5143 // for whatever reason, no reference chart is known
5144 // Probably panned out of the coverage region
5145 // If any charts are anywhere on-screen, choose the smallest
5146 // scale chart on the screen to be a new reference chart.
5147 int trial_index = -1;
5148 if (m_pCurrentStack->nEntry) {
5149 int trial_index =
5150 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5151 }
5152
5153 if (trial_index < 0) {
5154 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5155 if (full_screen_array.size())
5156 trial_index = full_screen_array[full_screen_array.size() - 1];
5157 }
5158
5159 if (trial_index >= 0) {
5160 m_pQuilt->SetReferenceChart(trial_index);
5161 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5162 VPoint.rotation);
5163 ReloadVP();
5164 }
5165#pragma GCC diagnostic pop
5166 }
5167 }
5168
5169 // Turn off bFollow only if the ownship has left the screen
5170 if (m_bFollow) {
5171 double offx, offy;
5172 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5173
5174 double offset_angle = atan2(offy, offx);
5175 double offset_distance = sqrt((offy * offy) + (offx * offx));
5176 double chart_angle = GetVPRotation();
5177 double target_angle = chart_angle - offset_angle;
5178 double d_east_mod = offset_distance * cos(target_angle);
5179 double d_north_mod = offset_distance * sin(target_angle);
5180
5181 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5182 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5183
5184 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5185 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5186 m_bFollow = false; // update the follow flag
5187 UpdateFollowButtonState();
5188 }
5189 }
5190
5191 Refresh(false);
5192
5193 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5194
5195 return true;
5196}
5197
5198void ChartCanvas::ReloadVP(bool b_adjust) {
5199 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5200
5201 LoadVP(VPoint, b_adjust);
5202}
5203
5204void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5205#ifdef ocpnUSE_GL
5206 if (g_bopengl && m_glcc) {
5207 m_glcc->Invalidate();
5208 if (m_glcc->GetSize() != GetSize()) {
5209 m_glcc->SetSize(GetSize());
5210 }
5211 } else
5212#endif
5213 {
5214 m_cache_vp.Invalidate();
5215 m_bm_cache_vp.Invalidate();
5216 }
5217
5218 VPoint.Invalidate();
5219
5220 if (m_pQuilt) m_pQuilt->Invalidate();
5221
5222 // Make sure that the Selected Group is sensible...
5223 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5224 // m_groupIndex = 0;
5225 // if( !CheckGroup( m_groupIndex ) )
5226 // m_groupIndex = 0;
5227
5228 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5229 vp.m_projection_type, b_adjust);
5230}
5231
5232void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5233 m_pQuilt->SetReferenceChart(dbIndex);
5234 VPoint.Invalidate();
5235 m_pQuilt->Invalidate();
5236}
5237
5238double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5239 if (m_pQuilt)
5240 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5241 else
5242 return vp.view_scale_ppm;
5243}
5244
5245// Verify and adjust the current reference chart,
5246// so that it will not lead to excessive overzoom or underzoom onscreen
5247int ChartCanvas::AdjustQuiltRefChart() {
5248 int ret = -1;
5249 if (m_pQuilt) {
5250 wxASSERT(ChartData);
5251 ChartBase *pc =
5252 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5253 if (pc) {
5254 double min_ref_scale =
5255 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5256 double max_ref_scale =
5257 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5258
5259 if (VPoint.chart_scale < min_ref_scale) {
5260 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5261 } else if (VPoint.chart_scale > max_ref_scale) {
5262 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5263 } else {
5264 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5265
5266 int ref_family = pc->GetChartFamily();
5267
5268 if (!brender_ok) {
5269 unsigned int target_stack_index = 0;
5270 int target_stack_index_check =
5271 m_pQuilt->GetExtendedStackIndexArray()
5272 [m_pQuilt->GetRefChartdbIndex()]; // Lookup
5273
5274 if (wxNOT_FOUND != target_stack_index_check)
5275 target_stack_index = target_stack_index_check;
5276
5277 int extended_array_count =
5278 m_pQuilt->GetExtendedStackIndexArray().size();
5279 while ((!brender_ok) &&
5280 ((int)target_stack_index < (extended_array_count - 1))) {
5281 target_stack_index++;
5282 int test_db_index =
5283 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5284
5285 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5286 IsChartQuiltableRef(test_db_index)) {
5287 // open the target, and check the min_scale
5288 ChartBase *ptest_chart =
5289 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5290 if (ptest_chart) {
5291 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5292 }
5293 }
5294 }
5295
5296 if (brender_ok) { // found a better reference chart
5297 int new_db_index =
5298 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5299 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5300 IsChartQuiltableRef(new_db_index)) {
5301 m_pQuilt->SetReferenceChart(new_db_index);
5302 ret = new_db_index;
5303 } else
5304 ret = m_pQuilt->GetRefChartdbIndex();
5305 } else
5306 ret = m_pQuilt->GetRefChartdbIndex();
5307
5308 } else
5309 ret = m_pQuilt->GetRefChartdbIndex();
5310 }
5311 } else
5312 ret = -1;
5313 }
5314
5315 return ret;
5316}
5317
5318void ChartCanvas::UpdateCanvasOnGroupChange(void) {
5319 delete m_pCurrentStack;
5320 m_pCurrentStack = new ChartStack;
5321 wxASSERT(ChartData);
5322 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5323 m_groupIndex);
5324
5325 if (m_pQuilt) {
5326 m_pQuilt->Compose(VPoint);
5327 SetFocus();
5328 }
5329}
5330
5331bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5332 double latNE, double lonNE) {
5333 // Center Point
5334 double latc = (latSW + latNE) / 2.0;
5335 double lonc = (lonSW + lonNE) / 2.0;
5336
5337 // Get scale in ppm (latitude)
5338 double ne_easting, ne_northing;
5339 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5340
5341 double sw_easting, sw_northing;
5342 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5343
5344 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5345
5346 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5347}
5348
5349bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5350 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5351 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5352}
5353
5354bool ChartCanvas::SetVPProjection(int projection) {
5355 if (!g_bopengl) // alternative projections require opengl
5356 return false;
5357
5358 // the view scale varies depending on geographic location and projection
5359 // rescale to keep the relative scale on the screen the same
5360 double prev_true_scale_ppm = m_true_scale_ppm;
5361 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5362 VPoint.skew, VPoint.rotation, projection) &&
5363 SetVPScale(wxMax(
5364 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5365 m_absolute_min_scale_ppm));
5366}
5367
5368bool ChartCanvas::SetViewPoint(double lat, double lon) {
5369 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5370 VPoint.rotation);
5371}
5372
5373bool ChartCanvas::SetVPRotation(double angle) {
5374 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5375 VPoint.skew, angle);
5376}
5377bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5378 double skew, double rotation, int projection,
5379 bool b_adjust, bool b_refresh) {
5380 bool b_ret = false;
5381 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5382 skew -= 2 * PI;
5383 // Any sensible change?
5384 if (VPoint.IsValid()) {
5385 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5386 (fabs(VPoint.skew - skew) < 1e-9) &&
5387 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5388 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5389 (VPoint.m_projection_type == projection ||
5390 projection == PROJECTION_UNKNOWN))
5391 return false;
5392 }
5393 if (VPoint.m_projection_type != projection)
5394 VPoint.InvalidateTransformCache(); // invalidate
5395
5396 // Take a local copy of the last viewport
5397 ViewPort last_vp = VPoint;
5398
5399 VPoint.skew = skew;
5400 VPoint.clat = lat;
5401 VPoint.clon = lon;
5402 VPoint.rotation = rotation;
5403 VPoint.view_scale_ppm = scale_ppm;
5404 if (projection != PROJECTION_UNKNOWN)
5405 VPoint.SetProjectionType(projection);
5406 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5407 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5408
5409 // don't allow latitude above 88 for mercator (90 is infinity)
5410 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5411 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5412 if (VPoint.clat > 89.5)
5413 VPoint.clat = 89.5;
5414 else if (VPoint.clat < -89.5)
5415 VPoint.clat = -89.5;
5416 }
5417
5418 // don't zoom out too far for transverse mercator polyconic until we resolve
5419 // issues
5420 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5421 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5422 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5423
5424 // SetVPRotation(rotation);
5425
5426 if (!g_bopengl) // tilt is not possible without opengl
5427 VPoint.tilt = 0;
5428
5429 if ((VPoint.pix_width <= 0) ||
5430 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5431 return false;
5432
5433 bool bwasValid = VPoint.IsValid();
5434 VPoint.Validate(); // Mark this ViewPoint as OK
5435
5436 // Has the Viewport scale changed? If so, invalidate the vp
5437 if (last_vp.view_scale_ppm != scale_ppm) {
5438 m_cache_vp.Invalidate();
5439 InvalidateGL();
5440 }
5441
5442 // A preliminary value, may be tweaked below
5443 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5444
5445 // recompute cursor position
5446 // and send to interested plugins if the mouse is actually in this window
5447 int mouseX = mouse_x;
5448 int mouseY = mouse_y;
5449 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5450 (mouseY < VPoint.pix_height)) {
5451 double lat, lon;
5452 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5453 m_cursor_lat = lat;
5454 m_cursor_lon = lon;
5455 SendCursorLatLonToAllPlugIns(lat, lon);
5456 }
5457
5458 if (!VPoint.b_quilt && m_singleChart) {
5459 VPoint.SetBoxes();
5460
5461 // Allow the chart to adjust the new ViewPort for performance optimization
5462 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5463 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5464
5465 // If there is a sensible change in the chart render, refresh the whole
5466 // screen
5467 if ((!m_cache_vp.IsValid()) ||
5468 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5469 Refresh(false);
5470 b_ret = true;
5471 } else {
5472 wxPoint cp_last, cp_this;
5473 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5474 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5475
5476 if (cp_last != cp_this) {
5477 Refresh(false);
5478 b_ret = true;
5479 }
5480 }
5481 // Create the stack
5482 if (m_pCurrentStack) {
5483 assert(ChartData != 0);
5484 int current_db_index;
5485 current_db_index =
5486 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5487
5488 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5489 m_groupIndex);
5490 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5491 }
5492
5493 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5494 }
5495
5496 // Handle the quilted case
5497 if (VPoint.b_quilt) {
5498 if (last_vp.view_scale_ppm != scale_ppm)
5499 m_pQuilt->InvalidateAllQuiltPatchs();
5500
5501 // Create the quilt
5502 if (ChartData /*&& ChartData->IsValid()*/) {
5503 if (!m_pCurrentStack) return false;
5504
5505 int current_db_index;
5506 current_db_index =
5507 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5508
5509 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5510 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5511
5512 // Check to see if the current quilt reference chart is in the new stack
5513 int current_ref_stack_index = -1;
5514 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5515 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5516 current_ref_stack_index = i;
5517 }
5518
5519 if (g_bFullScreenQuilt) {
5520 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5521 }
5522
5523 // We might need a new Reference Chart
5524 bool b_needNewRef = false;
5525
5526 // If the new stack does not contain the current ref chart....
5527 if ((-1 == current_ref_stack_index) &&
5528 (m_pQuilt->GetRefChartdbIndex() >= 0))
5529 b_needNewRef = true;
5530
5531 // Would the current Ref Chart be excessively underzoomed?
5532 // We need to check this here to be sure, since we cannot know where the
5533 // reference chart was assigned. For instance, the reference chart may
5534 // have been selected from the config file, or from a long jump with a
5535 // chart family switch implicit. Anyway, we check to be sure....
5536 bool renderable = true;
5537 ChartBase *referenceChart =
5538 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5539 if (referenceChart) {
5540 double chartMaxScale = referenceChart->GetNormalScaleMax(
5541 GetCanvasScaleFactor(), GetCanvasWidth());
5542 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5543 }
5544 if (!renderable) b_needNewRef = true;
5545
5546 // Need new refchart?
5547 if (b_needNewRef) {
5548 const ChartTableEntry &cte_ref =
5549 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5550 int target_scale = cte_ref.GetScale();
5551 int target_type = cte_ref.GetChartType();
5552 int candidate_stack_index;
5553
5554 // reset the ref chart in a way that does not lead to excessive
5555 // underzoom, for performance reasons Try to find a chart that is the
5556 // same type, and has a scale of just smaller than the current ref
5557 // chart
5558
5559 candidate_stack_index = 0;
5560 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5561 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5562 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5563 int candidate_scale = cte_candidate.GetScale();
5564 int candidate_type = cte_candidate.GetChartType();
5565
5566 if ((candidate_scale >= target_scale) &&
5567 (candidate_type == target_type)) {
5568 bool renderable = true;
5569 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5570 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5571 if (tentative_referenceChart) {
5572 double chartMaxScale =
5573 tentative_referenceChart->GetNormalScaleMax(
5574 GetCanvasScaleFactor(), GetCanvasWidth());
5575 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5576 }
5577
5578 if (renderable) break;
5579 }
5580
5581 candidate_stack_index++;
5582 }
5583
5584 // If that did not work, look for a chart of just larger scale and
5585 // same type
5586 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5587 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5588 while (candidate_stack_index >= 0) {
5589 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5590 if (idx >= 0) {
5591 const ChartTableEntry &cte_candidate =
5592 ChartData->GetChartTableEntry(idx);
5593 int candidate_scale = cte_candidate.GetScale();
5594 int candidate_type = cte_candidate.GetChartType();
5595
5596 if ((candidate_scale <= target_scale) &&
5597 (candidate_type == target_type))
5598 break;
5599 }
5600 candidate_stack_index--;
5601 }
5602 }
5603
5604 // and if that did not work, chose stack entry 0
5605 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5606 (candidate_stack_index < 0))
5607 candidate_stack_index = 0;
5608
5609 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5610
5611 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5612 }
5613
5614 if (!g_bopengl) {
5615 // Preset the VPoint projection type to match what the quilt projection
5616 // type will be
5617 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5618
5619 // Always keep the default Mercator projection if the reference chart is
5620 // not in the PatchList or the scale is too small for it to render.
5621
5622 bool renderable = true;
5623 ChartBase *referenceChart =
5624 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5625 if (referenceChart) {
5626 double chartMaxScale = referenceChart->GetNormalScaleMax(
5627 GetCanvasScaleFactor(), GetCanvasWidth());
5628 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5629 proj = ChartData->GetDBChartProj(ref_db_index);
5630 } else
5631 proj = PROJECTION_MERCATOR;
5632
5633 VPoint.b_MercatorProjectionOverride =
5634 (m_pQuilt->GetnCharts() == 0 || !renderable);
5635
5636 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5637
5638 VPoint.SetProjectionType(proj);
5639 }
5640
5641 VPoint.SetBoxes();
5642
5643 // If this quilt will be a perceptible delta from the existing quilt,
5644 // then refresh the entire screen
5645 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5646 // Allow the quilt to adjust the new ViewPort for performance
5647 // optimization This will normally be only a fractional (i.e.
5648 // sub-pixel) adjustment...
5649 if (b_adjust) {
5650 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5651 }
5652
5653 // ChartData->ClearCacheInUseFlags();
5654 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5655
5656 // wxStopWatch sw;
5657
5658#ifdef __ANDROID__
5659 // This is an optimization for panning on touch screen systems.
5660 // The quilt composition is deferred until the OnPaint() message gets
5661 // finally removed and processed from the message queue.
5662 // Takes advantage of the fact that touch-screen pan gestures are
5663 // usually short in distance,
5664 // so not requiring a full quilt rebuild until the pan gesture is
5665 // complete.
5666 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5667 // qDebug() << "Force compose";
5668 m_pQuilt->Compose(VPoint);
5669 } else {
5670 m_pQuilt->Invalidate();
5671 }
5672#else
5673 m_pQuilt->Compose(VPoint);
5674#endif
5675
5676 // printf("comp time %ld\n", sw.Time());
5677
5678 // If the extended chart stack has changed, invalidate any cached
5679 // render bitmap
5680 // if(m_pQuilt->GetXStackHash() != hash1) {
5681 // m_bm_cache_vp.Invalidate();
5682 // InvalidateGL();
5683 // }
5684
5685 ChartData->PurgeCacheUnusedCharts(0.7);
5686
5687 if (b_refresh) Refresh(false);
5688
5689 b_ret = true;
5690 }
5691 }
5692
5693 VPoint.skew = 0.; // Quilting supports 0 Skew
5694 } else if (!g_bopengl) {
5695 OcpnProjType projection = PROJECTION_UNKNOWN;
5696 if (m_singleChart) // viewport projection must match chart projection
5697 // without opengl
5698 projection = m_singleChart->GetChartProjectionType();
5699 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5700 VPoint.SetProjectionType(projection);
5701 }
5702
5703 // Has the Viewport projection changed? If so, invalidate the vp
5704 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5705 m_cache_vp.Invalidate();
5706 InvalidateGL();
5707 }
5708
5709 UpdateCanvasControlBar(); // Refresh the Piano
5710
5711 VPoint.chart_scale = 1.0; // fallback default value
5712
5713 /*if (!VPoint.GetBBox().GetValid())*/ VPoint.SetBoxes();
5714
5715 if (VPoint.GetBBox().GetValid()) {
5716 // Update the viewpoint reference scale
5717 if (m_singleChart)
5718 VPoint.ref_scale = m_singleChart->GetNativeScale();
5719 else {
5720#ifdef __ANDROID__
5721 // This is an optimization for panning on touch screen systems.
5722 // See above.
5723 // Quilt might not be fully composed at this point, so for cm93
5724 // the reference scale may not be known.
5725 // In this case, do not update the VP ref_scale.
5726 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5727 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5728 }
5729#else
5730 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5731#endif
5732 }
5733
5734 // Calculate the on-screen displayed actual scale
5735 // by a simple traverse northward from the center point
5736 // of roughly one eighth of the canvas height
5737 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5738
5739 double delta_check =
5740 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5741 delta_check /= 8.;
5742
5743 double check_point = wxMin(89., VPoint.clat);
5744
5745 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5746
5747 double rhumbDist;
5748 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5749 VPoint.clon, 0, &rhumbDist);
5750
5751 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5752 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5753 // Calculate the distance between r1 and r in physical pixels.
5754 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5755 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5756
5757 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5758
5759 // A fall back in case of very high zoom-out, giving delta_y == 0
5760 // which can probably only happen with vector charts
5761 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5762
5763 // Another fallback, for highly zoomed out charts
5764 // This adjustment makes the displayed TrueScale correspond to the
5765 // same algorithm used to calculate the chart zoom-out limit for
5766 // ChartDummy.
5767 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5768
5769 if (m_true_scale_ppm)
5770 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5771 else
5772 VPoint.chart_scale = 1.0;
5773
5774 // Create a nice renderable string
5775 double round_factor = 1000.;
5776 if (VPoint.chart_scale <= 1000.)
5777 round_factor = 10.;
5778 else if (VPoint.chart_scale <= 10000.)
5779 round_factor = 100.;
5780 else if (VPoint.chart_scale <= 100000.)
5781 round_factor = 1000.;
5782
5783 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5784 double retina_coef = 1;
5785#ifdef ocpnUSE_GL
5786#ifdef __WXOSX__
5787 if (g_bopengl) {
5788 retina_coef = GetContentScaleFactor();
5789 }
5790#endif
5791#endif
5792
5793 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5794 // rounded to the nearest 10, 100 or 1000.
5795 //
5796 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5797 // true_scale_display. That does not make sense. The chart scale should be
5798 // the same as the true scale within the limits of the rounding factor.
5799 double true_scale_display =
5800 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5801 wxString text;
5802
5803 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5804
5805 if (m_displayed_scale_factor > 10.0)
5806 text.Printf(_T("%s %4.0f (%1.0fx)"), _("Scale"), true_scale_display,
5807 m_displayed_scale_factor);
5808 else if (m_displayed_scale_factor > 1.0)
5809 text.Printf(_T("%s %4.0f (%1.1fx)"), _("Scale"), true_scale_display,
5810 m_displayed_scale_factor);
5811 else if (m_displayed_scale_factor > 0.1) {
5812 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5813 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5814 } else if (m_displayed_scale_factor > 0.01) {
5815 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5816 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5817 } else {
5818 text.Printf(
5819 _T("%s %4.0f (---)"), _("Scale"),
5820 true_scale_display); // Generally, no chart, so no chart scale factor
5821 }
5822
5823 m_scaleValue = true_scale_display;
5824 m_scaleText = text;
5825 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5826
5827 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5828 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5829 // Check to see if the text will fit in the StatusBar field...
5830 bool b_noshow = false;
5831 {
5832 int w = 0;
5833 int h;
5834 wxClientDC dc(parent_frame->GetStatusBar());
5835 if (dc.IsOk()) {
5836 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5837 dc.SetFont(*templateFont);
5838 dc.GetTextExtent(text, &w, &h);
5839
5840 // If text is too long for the allocated field, try to reduce the text
5841 // string a bit.
5842 wxRect rect;
5843 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5844 if (w && w > rect.width) {
5845 text.Printf(_T("%s (%1.1fx)"), _("Scale"),
5846 m_displayed_scale_factor);
5847 }
5848
5849 // Test again...if too big still, then give it up.
5850 dc.GetTextExtent(text, &w, &h);
5851
5852 if (w && w > rect.width) {
5853 b_noshow = true;
5854 }
5855 }
5856 }
5857
5858 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5859 }
5860 }
5861
5862 // Maintain member vLat/vLon
5863 m_vLat = VPoint.clat;
5864 m_vLon = VPoint.clon;
5865
5866 return b_ret;
5867}
5868
5869// Static Icon definitions for some symbols requiring
5870// scaling/rotation/translation Very specific wxDC draw commands are
5871// necessary to properly render these icons...See the code in
5872// ShipDraw()
5873
5874// This icon was adapted and scaled from the S52 Presentation Library
5875// version 3_03.
5876// Symbol VECGND02
5877
5878static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5879
5880// This ownship icon was adapted and scaled from the S52 Presentation
5881// Library version 3_03 Symbol OWNSHP05
5882static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5883 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5884
5885wxColour ChartCanvas::PredColor() {
5886 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5887 // visibility.
5888 if (SHIP_NORMAL == m_ownship_state)
5889 return GetGlobalColor(_T ( "URED" ));
5890
5891 else if (SHIP_LOWACCURACY == m_ownship_state)
5892 return GetGlobalColor(_T ( "YELO1" ));
5893
5894 return GetGlobalColor(_T ( "NODTA" ));
5895}
5896
5897wxColour ChartCanvas::ShipColor() {
5898 // Establish ship color
5899 // It changes color based on GPS and Chart accuracy/availability
5900
5901 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor(_T ( "GREY1" ));
5902
5903 if (SHIP_LOWACCURACY == m_ownship_state)
5904 return GetGlobalColor(_T ( "YELO1" ));
5905
5906 return GetGlobalColor(_T ( "URED" )); // default is OK
5907}
5908
5909void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5910 wxPoint2DDouble lShipMidPoint) {
5911 dc.SetPen(wxPen(PredColor(), 2));
5912
5913 if (SHIP_NORMAL == m_ownship_state)
5914 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5915 else
5916 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "YELO1" ))));
5917
5918 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5919 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5920
5921 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5922 lShipMidPoint.m_y);
5923 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5924 lShipMidPoint.m_y + 12);
5925}
5926
5927void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5928 wxPoint GPSOffsetPixels,
5929 wxPoint2DDouble lGPSPoint) {
5930 if (m_animationActive) return;
5931 // Develop a uniform length for course predictor line dash length, based on
5932 // physical display size Use this reference length to size all other graphics
5933 // elements
5934 float ref_dim = m_display_size_mm / 24;
5935 ref_dim = wxMin(ref_dim, 12);
5936 ref_dim = wxMax(ref_dim, 6);
5937
5938 wxColour cPred;
5939 cPred.Set(g_cog_predictor_color);
5940 if (cPred == wxNullColour) cPred = PredColor();
5941
5942 // Establish some graphic element line widths dependent on the platform
5943 // display resolution
5944 // double nominal_line_width_pix = wxMax(1.0,
5945 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5946 // not less than 1 pixel
5947 double nominal_line_width_pix = wxMax(
5948 1.0,
5949 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5950
5951 // If the calculated value is greater than the config file spec value, then
5952 // use it.
5953 if (nominal_line_width_pix > g_cog_predictor_width)
5954 g_cog_predictor_width = nominal_line_width_pix;
5955
5956 // Calculate ownship Position Predictor
5957 wxPoint lPredPoint, lHeadPoint;
5958
5959 float pCog = std::isnan(gCog) ? 0 : gCog;
5960 float pSog = std::isnan(gSog) ? 0 : gSog;
5961
5962 double pred_lat, pred_lon;
5963 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5964 &pred_lat, &pred_lon);
5965 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5966
5967 // test to catch the case where COG/HDG line crosses the screen
5968 LLBBox box;
5969
5970 // Should we draw the Head vector?
5971 // Compare the points lHeadPoint and lPredPoint
5972 // If they differ by more than n pixels, and the head vector is valid, then
5973 // render the head vector
5974
5975 float ndelta_pix = 10.;
5976 double hdg_pred_lat, hdg_pred_lon;
5977 bool b_render_hdt = false;
5978 if (!std::isnan(gHdt)) {
5979 // Calculate ownship Heading pointer as a predictor
5980 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5981 &hdg_pred_lon);
5982 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5983 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5984 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5985 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5986 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5987 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5988 }
5989 }
5990
5991 // draw course over ground if they are longer than the ship
5992 wxPoint lShipMidPoint;
5993 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5994 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5995 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5996 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5997
5998 if (lpp >= img_height / 2) {
5999 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
6000 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
6001 !std::isnan(gSog)) {
6002 // COG Predictor
6003 float dash_length = ref_dim;
6004 wxDash dash_long[2];
6005 dash_long[0] =
6006 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6007 g_cog_predictor_width); // Long dash , in mm <---------+
6008 dash_long[1] = dash_long[0] / 2.0; // Short gap
6009
6010 // On ultra-hi-res displays, do not allow the dashes to be greater than
6011 // 250, since it is defined as (char)
6012 if (dash_length > 250.) {
6013 dash_long[0] = 250. / g_cog_predictor_width;
6014 dash_long[1] = dash_long[0] / 2;
6015 }
6016
6017 wxPen ppPen2(cPred, g_cog_predictor_width,
6018 (wxPenStyle)g_cog_predictor_style);
6019 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6020 ppPen2.SetDashes(2, dash_long);
6021 dc.SetPen(ppPen2);
6022 dc.StrokeLine(
6023 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6024 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6025
6026 if (g_cog_predictor_width > 1) {
6027 float line_width = g_cog_predictor_width / 3.;
6028
6029 wxDash dash_long3[2];
6030 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6031 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6032
6033 wxPen ppPen3(GetGlobalColor(_T ( "UBLCK" )), wxMax(1, line_width),
6034 (wxPenStyle)g_cog_predictor_style);
6035 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6036 ppPen3.SetDashes(2, dash_long3);
6037 dc.SetPen(ppPen3);
6038 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6039 lGPSPoint.m_y + GPSOffsetPixels.y,
6040 lPredPoint.x + GPSOffsetPixels.x,
6041 lPredPoint.y + GPSOffsetPixels.y);
6042 }
6043
6044 if (g_cog_predictor_endmarker) {
6045 // Prepare COG predictor endpoint icon
6046 double png_pred_icon_scale_factor = .4;
6047 if (g_ShipScaleFactorExp > 1.0)
6048 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6049 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6050
6051 wxPoint icon[4];
6052
6053 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6054 (float)(lPredPoint.x - lShipMidPoint.x));
6055 cog_rad += (float)PI;
6056
6057 for (int i = 0; i < 4; i++) {
6058 int j = i * 2;
6059 double pxa = (double)(s_png_pred_icon[j]);
6060 double pya = (double)(s_png_pred_icon[j + 1]);
6061
6062 pya *= png_pred_icon_scale_factor;
6063 pxa *= png_pred_icon_scale_factor;
6064
6065 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6066 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6067
6068 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6069 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6070 }
6071
6072 // Render COG endpoint icon
6073 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), g_cog_predictor_width / 2,
6074 wxPENSTYLE_SOLID);
6075 dc.SetPen(ppPen1);
6076 dc.SetBrush(wxBrush(cPred));
6077
6078 dc.StrokePolygon(4, icon);
6079 }
6080 }
6081 }
6082
6083 // HDT Predictor
6084 if (b_render_hdt) {
6085 float hdt_dash_length = ref_dim * 0.4;
6086
6087 cPred.Set(g_ownship_HDTpredictor_color);
6088 if (cPred == wxNullColour) cPred = PredColor();
6089 float hdt_width =
6090 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6091 : g_cog_predictor_width * 0.8);
6092 wxDash dash_short[2];
6093 dash_short[0] =
6094 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6095 hdt_width); // Short dash , in mm <---------+
6096 dash_short[1] =
6097 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6098 hdt_width); // Short gap |
6099
6100 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6101 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6102 ppPen2.SetDashes(2, dash_short);
6103
6104 dc.SetPen(ppPen2);
6105 dc.StrokeLine(
6106 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6107 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6108
6109 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6110 dc.SetPen(ppPen1);
6111 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "GREY2" ))));
6112
6113 if (g_ownship_HDTpredictor_endmarker) {
6114 double nominal_circle_size_pixels = wxMax(
6115 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6116
6117 // Scale the circle to ChartScaleFactor, slightly softened....
6118 if (g_ShipScaleFactorExp > 1.0)
6119 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6120
6121 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6122 lHeadPoint.y + GPSOffsetPixels.y,
6123 nominal_circle_size_pixels / 2);
6124 }
6125 }
6126
6127 // Draw radar rings if activated
6128 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6129 double factor = 1.00;
6130 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6131 factor = 1 / 1.852;
6132 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6133 if (std::isnan(gSog))
6134 factor = 0.0;
6135 else
6136 factor = gSog / 60;
6137 }
6138 factor *= g_fNavAidRadarRingsStep;
6139
6140 double tlat, tlon;
6141 wxPoint r;
6142 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6143 GetCanvasPointPix(tlat, tlon, &r);
6144
6145 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6146 pow((double)(lGPSPoint.m_y - r.y), 2));
6147 int pix_radius = (int)lpp;
6148
6149 extern wxColor GetDimColor(wxColor c);
6150 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6151
6152 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6153
6154 dc.SetPen(ppPen1);
6155 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6156
6157 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6158 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6159 }
6160}
6161
6162void ChartCanvas::ComputeShipScaleFactor(
6163 float icon_hdt, int ownShipWidth, int ownShipLength,
6164 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6165 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6166 float screenResolution = m_pix_per_mm;
6167
6168 // Calculate the true ship length in exact pixels
6169 double ship_bow_lat, ship_bow_lon;
6170 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6171 &ship_bow_lat, &ship_bow_lon);
6172 wxPoint lShipBowPoint;
6173 wxPoint2DDouble b_point =
6174 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6175 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6176
6177 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6178 powf((float)(b_point.m_y - a_point.m_y), 2));
6179
6180 // And in mm
6181 float shipLength_mm = shipLength_px / screenResolution;
6182
6183 // Set minimum ownship drawing size
6184 float ownship_min_mm = g_n_ownship_min_mm;
6185 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6186
6187 // Calculate Nautical Miles distance from midships to gps antenna
6188 float hdt_ant = icon_hdt + 180.;
6189 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6190 float dx = g_n_gps_antenna_offset_x / 1852.;
6191 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6192 {
6193 hdt_ant = icon_hdt;
6194 dy = -dy;
6195 }
6196
6197 // If the drawn ship size is going to be clamped, adjust the gps antenna
6198 // offsets
6199 if (shipLength_mm < ownship_min_mm) {
6200 dy /= shipLength_mm / ownship_min_mm;
6201 dx /= shipLength_mm / ownship_min_mm;
6202 }
6203
6204 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6205
6206 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6207 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6208 &ship_mid_lon1);
6209
6210 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6211 &lShipMidPoint);
6212
6213 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6214 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6215
6216 float scale_factor = shipLength_px / ownShipLength;
6217
6218 // Calculate a scale factor that would produce a reasonably sized icon
6219 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6220
6221 // And choose the correct one
6222 scale_factor = wxMax(scale_factor, scale_factor_min);
6223
6224 scale_factor_y = scale_factor;
6225 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6226 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6227}
6228
6229void ChartCanvas::ShipDraw(ocpnDC &dc) {
6230 if (!GetVP().IsValid()) return;
6231
6232 wxPoint GPSOffsetPixels(0, 0);
6233 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6234
6235 // COG/SOG may be undefined in NMEA data stream
6236 float pCog = std::isnan(gCog) ? 0 : gCog;
6237 float pSog = std::isnan(gSog) ? 0 : gSog;
6238
6239 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6240
6241 lShipMidPoint = lGPSPoint;
6242
6243 // Draw the icon rotated to the COG
6244 // or to the Hdt if available
6245 float icon_hdt = pCog;
6246 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6247
6248 // COG may be undefined in NMEA data stream
6249 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6250
6251 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6252 // predictor
6253 double osd_head_lat, osd_head_lon;
6254 wxPoint osd_head_point;
6255
6256 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6257 &osd_head_lon);
6258
6259 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6260
6261 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6262 (float)(osd_head_point.x - lShipMidPoint.m_x));
6263 icon_rad += (float)PI;
6264
6265 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6266
6267 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6268 // nominal size and is just barely outside the viewport ....
6269 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6270
6271 // TODO: fix to include actual size of boat that will be rendered
6272 int img_height = 0;
6273 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6274 if (GetVP().chart_scale >
6275 300000) // According to S52, this should be 50,000
6276 {
6277 ShipDrawLargeScale(dc, lShipMidPoint);
6278 img_height = 20;
6279 } else {
6280 wxImage pos_image;
6281
6282 // Substitute user ownship image if found
6283 if (m_pos_image_user)
6284 pos_image = m_pos_image_user->Copy();
6285 else if (SHIP_NORMAL == m_ownship_state)
6286 pos_image = m_pos_image_red->Copy();
6287 if (SHIP_LOWACCURACY == m_ownship_state)
6288 pos_image = m_pos_image_yellow->Copy();
6289 else if (SHIP_NORMAL != m_ownship_state)
6290 pos_image = m_pos_image_grey->Copy();
6291
6292 // Substitute user ownship image if found
6293 if (m_pos_image_user) {
6294 pos_image = m_pos_image_user->Copy();
6295
6296 if (SHIP_LOWACCURACY == m_ownship_state)
6297 pos_image = m_pos_image_user_yellow->Copy();
6298 else if (SHIP_NORMAL != m_ownship_state)
6299 pos_image = m_pos_image_user_grey->Copy();
6300 }
6301
6302 img_height = pos_image.GetHeight();
6303
6304 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6305 g_OwnShipIconType > 0) // use large ship
6306 {
6307 int ownShipWidth = 22; // Default values from s_ownship_icon
6308 int ownShipLength = 84;
6309 if (g_OwnShipIconType == 1) {
6310 ownShipWidth = pos_image.GetWidth();
6311 ownShipLength = pos_image.GetHeight();
6312 }
6313
6314 float scale_factor_x, scale_factor_y;
6315 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6316 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6317 scale_factor_x, scale_factor_y);
6318
6319 if (g_OwnShipIconType == 1) { // Scaled bitmap
6320 pos_image.Rescale(ownShipWidth * scale_factor_x,
6321 ownShipLength * scale_factor_y,
6322 wxIMAGE_QUALITY_HIGH);
6323 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6324 wxImage rot_image =
6325 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6326
6327 // Simple sharpening algorithm.....
6328 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6329 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6330 if (rot_image.GetAlpha(ip, jp) > 64)
6331 rot_image.SetAlpha(ip, jp, 255);
6332
6333 wxBitmap os_bm(rot_image);
6334
6335 int w = os_bm.GetWidth();
6336 int h = os_bm.GetHeight();
6337 img_height = h;
6338
6339 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6340 lShipMidPoint.m_y - h / 2, true);
6341
6342 // Maintain dirty box,, missing in __WXMSW__ library
6343 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6344 lShipMidPoint.m_y - h / 2);
6345 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6346 lShipMidPoint.m_y - h / 2 + h);
6347 }
6348
6349 else if (g_OwnShipIconType == 2) { // Scaled Vector
6350 wxPoint ownship_icon[10];
6351
6352 for (int i = 0; i < 10; i++) {
6353 int j = i * 2;
6354 float pxa = (float)(s_ownship_icon[j]);
6355 float pya = (float)(s_ownship_icon[j + 1]);
6356 pya *= scale_factor_y;
6357 pxa *= scale_factor_x;
6358
6359 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6360 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6361
6362 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6363 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6364 }
6365
6366 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), 1, wxPENSTYLE_SOLID);
6367 dc.SetPen(ppPen1);
6368 dc.SetBrush(wxBrush(ShipColor()));
6369
6370 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6371
6372 // draw reference point (midships) cross
6373 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6374 ownship_icon[7].y);
6375 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6376 ownship_icon[9].y);
6377 }
6378
6379 img_height = ownShipLength * scale_factor_y;
6380
6381 // Reference point, where the GPS antenna is
6382 int circle_rad = 3;
6383 if (m_pos_image_user) circle_rad = 1;
6384
6385 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6386 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6387 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6388 } else { // Fixed bitmap icon.
6389 /* non opengl, or suboptimal opengl via ocpndc: */
6390 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6391 wxImage rot_image =
6392 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6393
6394 // Simple sharpening algorithm.....
6395 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6396 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6397 if (rot_image.GetAlpha(ip, jp) > 64)
6398 rot_image.SetAlpha(ip, jp, 255);
6399
6400 wxBitmap os_bm(rot_image);
6401
6402 if (g_ShipScaleFactorExp > 1) {
6403 wxImage scaled_image = os_bm.ConvertToImage();
6404 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6405 1.0; // soften the scale factor a bit
6406 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6407 scaled_image.GetHeight() * factor,
6408 wxIMAGE_QUALITY_HIGH));
6409 }
6410 int w = os_bm.GetWidth();
6411 int h = os_bm.GetHeight();
6412 img_height = h;
6413
6414 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6415 lShipMidPoint.m_y - h / 2, true);
6416
6417 // Reference point, where the GPS antenna is
6418 int circle_rad = 3;
6419 if (m_pos_image_user) circle_rad = 1;
6420
6421 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6422 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6423 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6424
6425 // Maintain dirty box,, missing in __WXMSW__ library
6426 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6427 lShipMidPoint.m_y - h / 2);
6428 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6429 lShipMidPoint.m_y - h / 2 + h);
6430 }
6431 } // ownship draw
6432 }
6433
6434 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6435}
6436
6437/* @ChartCanvas::CalcGridSpacing
6438 **
6439 ** Calculate the major and minor spacing between the lat/lon grid
6440 **
6441 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6442 *window
6443 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6444 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6445 ** @return [void]
6446 */
6447void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6448 float &MinorSpacing) {
6449 // table for calculating the distance between the grids
6450 // [0] view_scale ppm
6451 // [1] spacing between major grid lines in degrees
6452 // [2] spacing between minor grid lines in degrees
6453 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6454 {.000001f, 45.0f, 15.0f},
6455 {.0002f, 30.0f, 10.0f},
6456 {.0003f, 10.0f, 2.0f},
6457 {.0008f, 5.0f, 1.0f},
6458 {.001f, 2.0f, 30.0f / 60.0f},
6459 {.003f, 1.0f, 20.0f / 60.0f},
6460 {.006f, 0.5f, 10.0f / 60.0f},
6461 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6462 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6463 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6464 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6465 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6466 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6467 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6468 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6469
6470 unsigned int tabi;
6471 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6472 if (view_scale_ppm < lltab[tabi][0]) break;
6473 MajorSpacing = lltab[tabi][1]; // major latitude distance
6474 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6475 return;
6476}
6477/* @ChartCanvas::CalcGridText *************************************
6478 **
6479 ** Calculates text to display at the major grid lines
6480 **
6481 ** @param [r] latlon [float] latitude or longitude of grid line
6482 ** @param [r] spacing [float] distance between two major grid lines
6483 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6484 **
6485 ** @return
6486 */
6487
6488wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6489 int deg = (int)fabs(latlon); // degrees
6490 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6491 char postfix;
6492
6493 // calculate postfix letter (NSEW)
6494 if (latlon > 0.0) {
6495 if (bPostfix) {
6496 postfix = 'N';
6497 } else {
6498 postfix = 'E';
6499 }
6500 } else if (latlon < 0.0) {
6501 if (bPostfix) {
6502 postfix = 'S';
6503 } else {
6504 postfix = 'W';
6505 }
6506 } else {
6507 postfix = ' '; // no postfix for equator and greenwich
6508 }
6509 // calculate text, display minutes only if spacing is smaller than one degree
6510
6511 wxString ret;
6512 if (spacing >= 1.0) {
6513 ret.Printf(_T("%3d%c %c"), deg, 0x00b0, postfix);
6514 } else if (spacing >= (1.0 / 60.0)) {
6515 ret.Printf(_T("%3d%c%02.0f %c"), deg, 0x00b0, min, postfix);
6516 } else {
6517 ret.Printf(_T("%3d%c%02.2f %c"), deg, 0x00b0, min, postfix);
6518 }
6519
6520 return ret;
6521}
6522
6523/* @ChartCanvas::GridDraw *****************************************
6524 **
6525 ** Draws major and minor Lat/Lon Grid on the chart
6526 ** - distance between Grid-lm ines are calculated automatic
6527 ** - major grid lines will be across the whole chart window
6528 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6529 **
6530 ** @param [w] dc [wxDC&] the wx drawing context
6531 **
6532 ** @return [void]
6533 ************************************************************************/
6534void ChartCanvas::GridDraw(ocpnDC &dc) {
6535 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6536
6537 double nlat, elon, slat, wlon;
6538 float lat, lon;
6539 float dlon;
6540 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6541 wxCoord w, h;
6542 wxPen GridPen(GetGlobalColor(_T ( "SNDG1" )), 1, wxPENSTYLE_SOLID);
6543 dc.SetPen(GridPen);
6544 if (!m_pgridFont) SetupGridFont();
6545 dc.SetFont(*m_pgridFont);
6546 dc.SetTextForeground(GetGlobalColor(_T ( "SNDG1" )));
6547
6548 w = m_canvas_width;
6549 h = m_canvas_height;
6550
6551 GetCanvasPixPoint(0, 0, nlat,
6552 wlon); // get lat/lon of upper left point of the window
6553 GetCanvasPixPoint(w, h, slat,
6554 elon); // get lat/lon of lower right point of the window
6555 dlon =
6556 elon -
6557 wlon; // calculate how many degrees of longitude are shown in the window
6558 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6559 {
6560 dlon = dlon + 360.0;
6561 }
6562 // calculate distance between latitude grid lines
6563 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6564
6565 // calculate position of first major latitude grid line
6566 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6567
6568 // Draw Major latitude grid lines and text
6569 while (lat < nlat) {
6570 wxPoint r;
6571 wxString st =
6572 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6573 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6574 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6575 dc.DrawText(st, 0, r.y); // draw text
6576 lat = lat + gridlatMajor;
6577
6578 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6579 }
6580
6581 // calculate position of first minor latitude grid line
6582 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6583
6584 // Draw minor latitude grid lines
6585 while (lat < nlat) {
6586 wxPoint r;
6587 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6588 dc.DrawLine(0, r.y, 10, r.y, false);
6589 dc.DrawLine(w - 10, r.y, w, r.y, false);
6590 lat = lat + gridlatMinor;
6591 }
6592
6593 // calculate distance between grid lines
6594 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6595
6596 // calculate position of first major latitude grid line
6597 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6598
6599 // draw major longitude grid lines
6600 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6601 wxPoint r;
6602 wxString st = CalcGridText(lon, gridlonMajor, false);
6603 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6604 dc.DrawLine(r.x, 0, r.x, h, false);
6605 dc.DrawText(st, r.x, 0);
6606 lon = lon + gridlonMajor;
6607 if (lon > 180.0) {
6608 lon = lon - 360.0;
6609 }
6610
6611 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6612 }
6613
6614 // calculate position of first minor longitude grid line
6615 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6616 // draw minor longitude grid lines
6617 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6618 wxPoint r;
6619 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6620 dc.DrawLine(r.x, 0, r.x, 10, false);
6621 dc.DrawLine(r.x, h - 10, r.x, h, false);
6622 lon = lon + gridlonMinor;
6623 if (lon > 180.0) {
6624 lon = lon - 360.0;
6625 }
6626 }
6627}
6628
6629void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6630 if (0 ) {
6631 double blat, blon, tlat, tlon;
6632 wxPoint r;
6633
6634 int x_origin = m_bDisplayGrid ? 60 : 20;
6635 int y_origin = m_canvas_height - 50;
6636
6637 float dist;
6638 int count;
6639 wxPen pen1, pen2;
6640
6641 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6642 {
6643 dist = 10.0;
6644 count = 5;
6645 pen1 = wxPen(GetGlobalColor(_T ( "SNDG2" )), 3, wxPENSTYLE_SOLID);
6646 pen2 = wxPen(GetGlobalColor(_T ( "SNDG1" )), 3, wxPENSTYLE_SOLID);
6647 } else // Draw 1 mile scale as SCALEB10
6648 {
6649 dist = 1.0;
6650 count = 10;
6651 pen1 = wxPen(GetGlobalColor(_T ( "SCLBR" )), 3, wxPENSTYLE_SOLID);
6652 pen2 = wxPen(GetGlobalColor(_T ( "CHGRD" )), 3, wxPENSTYLE_SOLID);
6653 }
6654
6655 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6656 double rotation = -VPoint.rotation;
6657
6658 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6659 GetCanvasPointPix(tlat, tlon, &r);
6660 int l1 = (y_origin - r.y) / count;
6661
6662 for (int i = 0; i < count; i++) {
6663 int y = l1 * i;
6664 if (i & 1)
6665 dc.SetPen(pen1);
6666 else
6667 dc.SetPen(pen2);
6668
6669 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6670 }
6671 } else {
6672 double blat, blon, tlat, tlon;
6673
6674 int x_origin = 5.0 * GetPixPerMM();
6675 int chartbar_height = GetChartbarHeight();
6676 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6677 // if (style->chartStatusWindowTransparent)
6678 // chartbar_height = 0;
6679 int y_origin = m_canvas_height - chartbar_height - 5;
6680#ifdef __WXOSX__
6681 if (!g_bopengl)
6682 y_origin =
6683 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6684#endif
6685
6686 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6687 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6688
6689 double d;
6690 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6691 d /= 2;
6692
6693 int unit = g_iDistanceFormat;
6694 if (d < .5 &&
6695 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6696 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6697
6698 // nice number
6699 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6700 float places = floor(logdist), rem = logdist - places;
6701 dist = pow(10, places);
6702
6703 if (rem < .2)
6704 dist /= 5;
6705 else if (rem < .5)
6706 dist /= 2;
6707
6708 wxString s = wxString::Format(_T("%g "), dist) + getUsrDistanceUnit(unit);
6709 wxPen pen1 = wxPen(GetGlobalColor(_T ( "UBLCK" )), 3, wxPENSTYLE_SOLID);
6710 double rotation = -VPoint.rotation;
6711
6712 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6713 &tlat, &tlon);
6714 wxPoint r;
6715 GetCanvasPointPix(tlat, tlon, &r);
6716 int l1 = r.x - x_origin;
6717
6718 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6719 12); // Store this for later reference
6720
6721 dc.SetPen(pen1);
6722
6723 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6724 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6725 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6726
6727 if (!m_pgridFont) SetupGridFont();
6728 dc.SetFont(*m_pgridFont);
6729 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
6730 int w, h;
6731 dc.GetTextExtent(s, &w, &h);
6732 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6733 if (g_bopengl) {
6734 w /= dpi_factor;
6735 h /= dpi_factor;
6736 }
6737 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6738 }
6739}
6740
6741void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6742 // Constants?
6743 double da_min = 2.;
6744 double da_max = 6.;
6745 double ra_min = 0.;
6746 double ra_max = 40.;
6747
6748 wxPen pen_save = dc.GetPen();
6749
6750 wxDateTime now = wxDateTime::Now();
6751
6752 dc.SetPen(pen);
6753
6754 int x0, y0, x1, y1;
6755
6756 x0 = x1 = x + radius; // Start point
6757 y0 = y1 = y;
6758 double angle = 0.;
6759 int i = 0;
6760
6761 while (angle < 360.) {
6762 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6763 angle += da;
6764
6765 if (angle > 360.) angle = 360.;
6766
6767 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6768
6769 double r;
6770 if (i & 1)
6771 r = radius + ra;
6772 else
6773 r = radius - ra;
6774
6775 x1 = (int)(x + cos(angle * PI / 180.) * r);
6776 y1 = (int)(y + sin(angle * PI / 180.) * r);
6777
6778 dc.DrawLine(x0, y0, x1, y1);
6779
6780 x0 = x1;
6781 y0 = y1;
6782
6783 i++;
6784 }
6785
6786 dc.DrawLine(x + radius, y, x1, y1); // closure
6787
6788 dc.SetPen(pen_save);
6789}
6790
6791static bool bAnchorSoundPlaying = false;
6792
6793static void onAnchorSoundFinished(void *ptr) {
6794 g_anchorwatch_sound->UnLoad();
6795 bAnchorSoundPlaying = false;
6796}
6797
6798void ChartCanvas::AlertDraw(ocpnDC &dc) {
6799 // Visual and audio alert for anchorwatch goes here
6800 bool play_sound = false;
6801 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6802 if (AnchorAlertOn1) {
6803 wxPoint TargetPoint;
6804 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
6805 &TargetPoint);
6806 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6807 TargetPoint.y, 100);
6808 play_sound = true;
6809 }
6810 } else
6811 AnchorAlertOn1 = false;
6812
6813 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6814 if (AnchorAlertOn2) {
6815 wxPoint TargetPoint;
6816 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
6817 &TargetPoint);
6818 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6819 TargetPoint.y, 100);
6820 play_sound = true;
6821 }
6822 } else
6823 AnchorAlertOn2 = false;
6824
6825 if (play_sound) {
6826 if (!bAnchorSoundPlaying) {
6827 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6828 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6829 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6830 if (g_anchorwatch_sound->IsOk()) {
6831 bAnchorSoundPlaying = true;
6832 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6833 g_anchorwatch_sound->Play();
6834 }
6835 }
6836 }
6837}
6838
6839void ChartCanvas::UpdateShips() {
6840 // Get the rectangle in the current dc which bounds the "ownship" symbol
6841
6842 wxClientDC dc(this);
6843 if (!dc.IsOk()) return;
6844
6845 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6846 if (!test_bitmap.IsOk()) return;
6847
6848 wxMemoryDC temp_dc(test_bitmap);
6849
6850 temp_dc.ResetBoundingBox();
6851 temp_dc.DestroyClippingRegion();
6852 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6853
6854 // Draw the ownship on the temp_dc
6855 ocpnDC ocpndc = ocpnDC(temp_dc);
6856 ShipDraw(ocpndc);
6857
6858 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6859 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6860 if (p) {
6861 wxPoint px;
6862 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6863 ocpndc.CalcBoundingBox(px.x, px.y);
6864 }
6865 }
6866
6867 ship_draw_rect =
6868 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6869 temp_dc.MaxY() - temp_dc.MinY());
6870
6871 wxRect own_ship_update_rect = ship_draw_rect;
6872
6873 if (!own_ship_update_rect.IsEmpty()) {
6874 // The required invalidate rectangle is the union of the last drawn
6875 // rectangle and this drawn rectangle
6876 own_ship_update_rect.Union(ship_draw_last_rect);
6877 own_ship_update_rect.Inflate(2);
6878 }
6879
6880 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6881
6882 ship_draw_last_rect = ship_draw_rect;
6883
6884 temp_dc.SelectObject(wxNullBitmap);
6885}
6886
6887void ChartCanvas::UpdateAlerts() {
6888 // Get the rectangle in the current dc which bounds the detected Alert
6889 // targets
6890
6891 // Use this dc
6892 wxClientDC dc(this);
6893
6894 // Get dc boundary
6895 int sx, sy;
6896 dc.GetSize(&sx, &sy);
6897
6898 // Need a bitmap
6899 wxBitmap test_bitmap(sx, sy, -1);
6900
6901 // Create a memory DC
6902 wxMemoryDC temp_dc;
6903 temp_dc.SelectObject(test_bitmap);
6904
6905 temp_dc.ResetBoundingBox();
6906 temp_dc.DestroyClippingRegion();
6907 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6908
6909 // Draw the Alert Targets on the temp_dc
6910 ocpnDC ocpndc = ocpnDC(temp_dc);
6911 AlertDraw(ocpndc);
6912
6913 // Retrieve the drawing extents
6914 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6915 temp_dc.MaxX() - temp_dc.MinX(),
6916 temp_dc.MaxY() - temp_dc.MinY());
6917
6918 if (!alert_rect.IsEmpty())
6919 alert_rect.Inflate(2); // clear all drawing artifacts
6920
6921 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6922 // The required invalidate rectangle is the union of the last drawn
6923 // rectangle and this drawn rectangle
6924 wxRect alert_update_rect = alert_draw_rect;
6925 alert_update_rect.Union(alert_rect);
6926
6927 // Invalidate the rectangular region
6928 RefreshRect(alert_update_rect, false);
6929 }
6930
6931 // Save this rectangle for next time
6932 alert_draw_rect = alert_rect;
6933
6934 temp_dc.SelectObject(wxNullBitmap); // clean up
6935}
6936
6937void ChartCanvas::UpdateAIS() {
6938 if (!g_pAIS) return;
6939
6940 // Get the rectangle in the current dc which bounds the detected AIS targets
6941
6942 // Use this dc
6943 wxClientDC dc(this);
6944
6945 // Get dc boundary
6946 int sx, sy;
6947 dc.GetSize(&sx, &sy);
6948
6949 wxRect ais_rect;
6950
6951 // How many targets are there?
6952
6953 // If more than "some number", it will be cheaper to refresh the entire
6954 // screen than to build update rectangles for each target.
6955 if (g_pAIS->GetTargetList().size() > 10) {
6956 ais_rect = wxRect(0, 0, sx, sy); // full screen
6957 } else {
6958 // Need a bitmap
6959 wxBitmap test_bitmap(sx, sy, -1);
6960
6961 // Create a memory DC
6962 wxMemoryDC temp_dc;
6963 temp_dc.SelectObject(test_bitmap);
6964
6965 temp_dc.ResetBoundingBox();
6966 temp_dc.DestroyClippingRegion();
6967 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6968
6969 // Draw the AIS Targets on the temp_dc
6970 ocpnDC ocpndc = ocpnDC(temp_dc);
6971 AISDraw(ocpndc, GetVP(), this);
6972 AISDrawAreaNotices(ocpndc, GetVP(), this);
6973
6974 // Retrieve the drawing extents
6975 ais_rect =
6976 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6977 temp_dc.MaxY() - temp_dc.MinY());
6978
6979 if (!ais_rect.IsEmpty())
6980 ais_rect.Inflate(2); // clear all drawing artifacts
6981
6982 temp_dc.SelectObject(wxNullBitmap); // clean up
6983 }
6984
6985 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6986 // The required invalidate rectangle is the union of the last drawn
6987 // rectangle and this drawn rectangle
6988 wxRect ais_update_rect = ais_draw_rect;
6989 ais_update_rect.Union(ais_rect);
6990
6991 // Invalidate the rectangular region
6992 RefreshRect(ais_update_rect, false);
6993 }
6994
6995 // Save this rectangle for next time
6996 ais_draw_rect = ais_rect;
6997}
6998
6999void ChartCanvas::ToggleCPAWarn() {
7000 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
7001 wxString mess;
7002 if (g_bCPAWarn) {
7003 g_bTCPA_Max = true;
7004 mess = _("ON");
7005 } else {
7006 g_bTCPA_Max = false;
7007 mess = _("OFF");
7008 }
7009 // Print to status bar if available.
7010 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
7011 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7012 } else {
7013 if (!g_AisFirstTimeUse) {
7014 OCPNMessageBox(this,
7015 _("CPA Alarm is switched") + _T(" ") + mess.MakeLower(),
7016 _("CPA") + _T(" ") + mess, 4, 4);
7017 }
7018 }
7019}
7020
7021void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7022
7023void ChartCanvas::OnSize(wxSizeEvent &event) {
7024 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7025 // GetClientSize returns the size of the canvas area in logical pixels.
7026 GetClientSize(&m_canvas_width, &m_canvas_height);
7027
7028#ifdef __WXOSX__
7029 // Support scaled HDPI displays.
7030 m_displayScale = GetContentScaleFactor();
7031#endif
7032
7033 // Convert to physical pixels.
7034 m_canvas_width *= m_displayScale;
7035 m_canvas_height *= m_displayScale;
7036
7037 // Resize the current viewport
7038 VPoint.pix_width = m_canvas_width;
7039 VPoint.pix_height = m_canvas_height;
7040 VPoint.SetPixelScale(m_displayScale);
7041
7042 // Get some canvas metrics
7043
7044 // Rescale to current value, in order to rebuild VPoint data
7045 // structures for new canvas size
7047
7048 m_absolute_min_scale_ppm =
7049 m_canvas_width /
7050 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7051
7052 // Inform the parent Frame that I am being resized...
7053 gFrame->ProcessCanvasResize();
7054
7055 // if MUIBar is active, size the bar
7056 // if(g_useMUI && !m_muiBar){ // rebuild if
7057 // necessary
7058 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7059 // m_muiBarHOSize = m_muiBar->GetSize();
7060 // }
7061
7062 if (m_muiBar) {
7063 SetMUIBarPosition();
7064 UpdateFollowButtonState();
7065 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7066 }
7067
7068 // Set up the scroll margins
7069 xr_margin = m_canvas_width * 95 / 100;
7070 xl_margin = m_canvas_width * 5 / 100;
7071 yt_margin = m_canvas_height * 5 / 100;
7072 yb_margin = m_canvas_height * 95 / 100;
7073
7074 if (m_pQuilt)
7075 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7076
7077 // Resize the scratch BM
7078 delete pscratch_bm;
7079 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7080 m_brepaint_piano = true;
7081
7082 // Resize the Route Calculation BM
7083 m_dc_route.SelectObject(wxNullBitmap);
7084 delete proute_bm;
7085 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7086 m_dc_route.SelectObject(*proute_bm);
7087
7088 // Resize the saved Bitmap
7089 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7090
7091 // Resize the working Bitmap
7092 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7093
7094 // Rescale again, to capture all the changes for new canvas size
7096
7097#ifdef ocpnUSE_GL
7098 if (/*g_bopengl &&*/ m_glcc) {
7099 // FIXME (dave) This can go away?
7100 m_glcc->OnSize(event);
7101 }
7102#endif
7103
7104 FormatPianoKeys();
7105 // Invalidate the whole window
7106 ReloadVP();
7107}
7108
7109void ChartCanvas::ProcessNewGUIScale() {
7110 // m_muiBar->Hide();
7111 delete m_muiBar;
7112 m_muiBar = 0;
7113
7114 CreateMUIBar();
7115}
7116
7117void ChartCanvas::CreateMUIBar() {
7118 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7119
7120 // We need to update the m_bENCGroup flag, at least for the initial creation
7121 // of a MUIBar
7122 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7123
7124 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7125 m_muiBar->SetColorScheme(m_cs);
7126 m_muiBarHOSize = m_muiBar->m_size;
7127 }
7128
7129 if (m_muiBar) {
7130 SetMUIBarPosition();
7131 UpdateFollowButtonState();
7132 m_muiBar->UpdateDynamicValues();
7133 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7134 }
7135}
7136
7137void ChartCanvas::SetMUIBarPosition() {
7138 // if MUIBar is active, size the bar
7139 if (m_muiBar) {
7140 // We estimate the piano width based on the canvas width
7141 int pianoWidth = GetClientSize().x * 0.6f;
7142 // If the piano already exists, we can use its exact width
7143 // if(m_Piano)
7144 // pianoWidth = m_Piano->GetWidth();
7145
7146 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7147 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7148 delete m_muiBar;
7149 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7150 m_muiBar->SetColorScheme(m_cs);
7151 }
7152 }
7153
7154 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7155 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7156 delete m_muiBar;
7157 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7158 m_muiBar->SetColorScheme(m_cs);
7159 }
7160 }
7161
7162 m_muiBar->SetBestPosition();
7163 }
7164}
7165
7166void ChartCanvas::DestroyMuiBar() {
7167 if (m_muiBar) {
7168 delete m_muiBar;
7169 m_muiBar = NULL;
7170 }
7171}
7172
7173void ChartCanvas::ShowCompositeInfoWindow(
7174 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7175 if (n_charts > 0) {
7176 if (NULL == m_pCIWin) {
7177 m_pCIWin = new ChInfoWin(this);
7178 m_pCIWin->Hide();
7179 }
7180
7181 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7182 wxString s;
7183
7184 s = _("Composite of ");
7185
7186 wxString s1;
7187 s1.Printf("%d ", n_charts);
7188 if (n_charts > 1)
7189 s1 += _("charts");
7190 else
7191 s1 += _("chart");
7192 s += s1;
7193 s += '\n';
7194
7195 s1.Printf(_("Chart scale"));
7196 s1 += ": ";
7197 wxString s2;
7198 s2.Printf("1:%d\n", scale);
7199 s += s1;
7200 s += s2;
7201
7202 s1 = _("Zoom in for more information");
7203 s += s1;
7204 s += '\n';
7205
7206 int char_width = s1.Length();
7207 int char_height = 3;
7208
7209 if (g_bChartBarEx) {
7210 s += '\n';
7211 int j = 0;
7212 for (int i : index_vector) {
7213 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7214 wxString path = cte.GetFullSystemPath();
7215 s += path;
7216 s += '\n';
7217 char_height++;
7218 char_width = wxMax(char_width, path.Length());
7219 if (j++ >= 9) break;
7220 }
7221 if (j >= 9) {
7222 s += " .\n .\n .\n";
7223 char_height += 3;
7224 }
7225 s += '\n';
7226 char_height += 1;
7227
7228 char_width += 4; // Fluff
7229 }
7230
7231 m_pCIWin->SetString(s);
7232
7233 m_pCIWin->FitToChars(char_width, char_height);
7234
7235 wxPoint p;
7236 p.x = x / GetContentScaleFactor();
7237 if ((p.x + m_pCIWin->GetWinSize().x) >
7238 (m_canvas_width / GetContentScaleFactor()))
7239 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7240 m_pCIWin->GetWinSize().x) /
7241 2; // centered
7242
7243 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7244 4 - m_pCIWin->GetWinSize().y;
7245
7246 m_pCIWin->dbIndex = 0;
7247 m_pCIWin->chart_scale = 0;
7248 m_pCIWin->SetPosition(p);
7249 m_pCIWin->SetBitmap();
7250 m_pCIWin->Refresh();
7251 m_pCIWin->Show();
7252 }
7253 } else {
7254 HideChartInfoWindow();
7255 }
7256}
7257
7258void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7259 if (dbIndex >= 0) {
7260 if (NULL == m_pCIWin) {
7261 m_pCIWin = new ChInfoWin(this);
7262 m_pCIWin->Hide();
7263 }
7264
7265 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7266 wxString s;
7267 ChartBase *pc = NULL;
7268
7269 // TOCTOU race but worst case will reload chart.
7270 // need to lock it or the background spooler may evict charts in
7271 // OpenChartFromDBAndLock
7272 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7273 pc = ChartData->OpenChartFromDBAndLock(
7274 dbIndex, FULL_INIT); // this must come from cache
7275
7276 int char_width, char_height;
7277 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7278 if (pc) ChartData->UnLockCacheChart(dbIndex);
7279
7280 m_pCIWin->SetString(s);
7281 m_pCIWin->FitToChars(char_width, char_height);
7282
7283 wxPoint p;
7284 p.x = x / GetContentScaleFactor();
7285 if ((p.x + m_pCIWin->GetWinSize().x) >
7286 (m_canvas_width / GetContentScaleFactor()))
7287 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7288 m_pCIWin->GetWinSize().x) /
7289 2; // centered
7290
7291 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7292 4 - m_pCIWin->GetWinSize().y;
7293
7294 m_pCIWin->dbIndex = dbIndex;
7295 m_pCIWin->SetPosition(p);
7296 m_pCIWin->SetBitmap();
7297 m_pCIWin->Refresh();
7298 m_pCIWin->Show();
7299 }
7300 } else {
7301 HideChartInfoWindow();
7302 }
7303}
7304
7305void ChartCanvas::HideChartInfoWindow(void) {
7306 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7307 m_pCIWin->Hide();
7308 m_pCIWin->Destroy();
7309 m_pCIWin = NULL;
7310
7311#ifdef __ANDROID__
7312 androidForceFullRepaint();
7313#endif
7314 }
7315}
7316
7317void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7318 wxMouseEvent ev(wxEVT_MOTION);
7319 ev.m_x = mouse_x;
7320 ev.m_y = mouse_y;
7321 ev.m_leftDown = mouse_leftisdown;
7322
7323 wxEvtHandler *evthp = GetEventHandler();
7324
7325 ::wxPostEvent(evthp, ev);
7326}
7327
7328void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7329 if ((m_panx_target_final - m_panx_target_now) ||
7330 (m_pany_target_final - m_pany_target_now)) {
7331 DoTimedMovementTarget();
7332 } else
7333 DoTimedMovement();
7334}
7335
7336void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7337
7338bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7339 int delta) {
7340 if (m_disable_edge_pan) return false;
7341
7342 bool bft = false;
7343 int pan_margin = m_canvas_width * margin / 100;
7344 int pan_timer_set = 200;
7345 double pan_delta = GetVP().pix_width * delta / 100;
7346 int pan_x = 0;
7347 int pan_y = 0;
7348
7349 if (x > m_canvas_width - pan_margin) {
7350 bft = true;
7351 pan_x = pan_delta;
7352 }
7353
7354 else if (x < pan_margin) {
7355 bft = true;
7356 pan_x = -pan_delta;
7357 }
7358
7359 if (y < pan_margin) {
7360 bft = true;
7361 pan_y = -pan_delta;
7362 }
7363
7364 else if (y > m_canvas_height - pan_margin) {
7365 bft = true;
7366 pan_y = pan_delta;
7367 }
7368
7369 // Of course, if dragging, and the mouse left button is not down, we must
7370 // stop the event injection
7371 if (bdragging) {
7372 if (!g_btouch) {
7373 wxMouseState state = ::wxGetMouseState();
7374#if wxCHECK_VERSION(3, 0, 0)
7375 if (!state.LeftIsDown())
7376#else
7377 if (!state.LeftDown())
7378#endif
7379 bft = false;
7380 }
7381 }
7382 if ((bft) && !pPanTimer->IsRunning()) {
7383 PanCanvas(pan_x, pan_y);
7384 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7385 return true;
7386 }
7387
7388 // This mouse event must not be due to pan timer event injector
7389 // Mouse is out of the pan zone, so prevent any orphan event injection
7390 if ((!bft) && pPanTimer->IsRunning()) {
7391 pPanTimer->Stop();
7392 }
7393
7394 return (false);
7395}
7396
7397// Look for waypoints at the current position.
7398// Used to determine what a mouse event should act on.
7399
7400void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7401 bool setBeingEdited) {
7402 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7403 m_pRoutePointEditTarget = NULL;
7404 m_pFoundPoint = NULL;
7405
7406 SelectItem *pFind = NULL;
7407 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7408 SelectableItemList SelList = pSelect->FindSelectionList(
7409 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7410 wxSelectableItemListNode *node = SelList.GetFirst();
7411 while (node) {
7412 pFind = node->GetData();
7413
7414 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7415
7416 // Get an array of all routes using this point
7417 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7418 // TODO: delete m_pEditRouteArray after use?
7419
7420 // Use route array to determine actual visibility for the point
7421 bool brp_viz = false;
7422 if (m_pEditRouteArray) {
7423 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7424 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7425 if (pr->IsVisible()) {
7426 brp_viz = true;
7427 break;
7428 }
7429 }
7430 } else
7431 brp_viz = frp->IsVisible(); // isolated point
7432
7433 if (brp_viz) {
7434 // Use route array to rubberband all affected routes
7435 if (m_pEditRouteArray) // Editing Waypoint as part of route
7436 {
7437 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7438 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7439 pr->m_bIsBeingEdited = setBeingEdited;
7440 }
7441 m_bRouteEditing = setBeingEdited;
7442 } else // editing Mark
7443 {
7444 frp->m_bRPIsBeingEdited = setBeingEdited;
7445 m_bMarkEditing = setBeingEdited;
7446 }
7447
7448 m_pRoutePointEditTarget = frp;
7449 m_pFoundPoint = pFind;
7450 break; // out of the while(node)
7451 }
7452
7453 node = node->GetNext();
7454 } // while (node)
7455}
7456
7457void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7458 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7459 singleClickEventIsValid = false;
7460 m_DoubleClickTimer->Stop();
7461}
7462
7463bool leftIsDown;
7464
7465bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7466 if (!m_bChartDragging && !m_bDrawingRoute) {
7467 /*
7468 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7469 * mouse event coordinates are in logical pixels.
7470 */
7471 if (m_Compass && m_Compass->IsShown()) {
7472 wxRect logicalRect = m_Compass->GetLogicalRect();
7473 bool isInCompass = logicalRect.Contains(event.GetPosition());
7474 if (isInCompass) {
7475 if (m_Compass->MouseEvent(event)) {
7476 cursor_region = CENTER;
7477 if (!g_btouch) SetCanvasCursor(event);
7478 return true;
7479 }
7480 }
7481 }
7482
7483 if (m_notification_button && m_notification_button->IsShown()) {
7484 wxRect logicalRect = m_notification_button->GetLogicalRect();
7485 bool isinButton = logicalRect.Contains(event.GetPosition());
7486 if (isinButton) {
7487 SetCursor(*pCursorArrow);
7488 if (event.LeftDown()) HandleNotificationMouseClick();
7489 return true;
7490 }
7491 }
7492
7493 if (MouseEventToolbar(event)) return true;
7494
7495 if (MouseEventChartBar(event)) return true;
7496
7497 if (MouseEventMUIBar(event)) return true;
7498
7499 if (MouseEventIENCBar(event)) return true;
7500 }
7501 return false;
7502}
7503
7504void ChartCanvas::HandleNotificationMouseClick() {
7505 if (!m_NotificationsList) {
7506 m_NotificationsList = new NotificationsList(this);
7507
7508 // calculate best size for Notification list
7509 m_NotificationsList->RecalculateSize();
7510 m_NotificationsList->Hide();
7511 }
7512
7513 if (m_NotificationsList->IsShown()) {
7514 m_NotificationsList->Hide();
7515 } else {
7516 m_NotificationsList->RecalculateSize();
7517 m_NotificationsList->ReloadNotificationList();
7518 m_NotificationsList->Show();
7519 }
7520}
7521bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7522 if (!g_bShowChartBar) return false;
7523
7524 if (!m_Piano->MouseEvent(event)) return false;
7525
7526 cursor_region = CENTER;
7527 if (!g_btouch) SetCanvasCursor(event);
7528 return true;
7529}
7530
7531bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7532 if (!IsPrimaryCanvas()) return false;
7533
7534 if (g_MainToolbar) {
7535 if (!g_MainToolbar->MouseEvent(event))
7536 return false;
7537 else
7538 g_MainToolbar->RefreshToolbar();
7539 }
7540
7541 cursor_region = CENTER;
7542 if (!g_btouch) SetCanvasCursor(event);
7543 return true;
7544}
7545
7546bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7547 if (!IsPrimaryCanvas()) return false;
7548
7549 if (g_iENCToolbar) {
7550 if (!g_iENCToolbar->MouseEvent(event))
7551 return false;
7552 else {
7553 g_iENCToolbar->RefreshToolbar();
7554 return true;
7555 }
7556 }
7557 return false;
7558}
7559
7560bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7561 if (m_muiBar) {
7562 if (!m_muiBar->MouseEvent(event)) return false;
7563 }
7564
7565 cursor_region = CENTER;
7566 if (!g_btouch) SetCanvasCursor(event);
7567 if (m_muiBar)
7568 return true;
7569 else
7570 return false;
7571}
7572
7573bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7574 int x, y;
7575
7576 bool bret = false;
7577
7578 event.GetPosition(&x, &y);
7579
7580 x *= m_displayScale;
7581 y *= m_displayScale;
7582
7583 m_MouseDragging = event.Dragging();
7584
7585 // Some systems produce null drag events, where the pointer position has not
7586 // changed from the previous value. Detect this case, and abort further
7587 // processing (FS#1748)
7588#ifdef __WXMSW__
7589 if (event.Dragging()) {
7590 if ((x == mouse_x) && (y == mouse_y)) return true;
7591 }
7592#endif
7593
7594 mouse_x = x;
7595 mouse_y = y;
7596 mouse_leftisdown = event.LeftDown();
7598
7599 // Establish the event region
7600 cursor_region = CENTER;
7601
7602 int chartbar_height = GetChartbarHeight();
7603
7604 if (m_Compass && m_Compass->IsShown() &&
7605 m_Compass->GetRect().Contains(event.GetPosition())) {
7606 cursor_region = CENTER;
7607 } else if (x > xr_margin) {
7608 cursor_region = MID_RIGHT;
7609 } else if (x < xl_margin) {
7610 cursor_region = MID_LEFT;
7611 } else if (y > yb_margin - chartbar_height &&
7612 y < m_canvas_height - chartbar_height) {
7613 cursor_region = MID_TOP;
7614 } else if (y < yt_margin) {
7615 cursor_region = MID_BOT;
7616 } else {
7617 cursor_region = CENTER;
7618 }
7619
7620 if (!g_btouch) SetCanvasCursor(event);
7621
7622 // Protect from leftUp's coming from event handlers in child
7623 // windows who return focus to the canvas.
7624 leftIsDown = event.LeftDown();
7625
7626#ifndef __WXOSX__
7627 if (event.LeftDown()) {
7628 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7629 // The menu bar is temporarily visible due to alt having been pressed.
7630 // Clicking will hide it, and do nothing else.
7631 g_bTempShowMenuBar = false;
7632 parent_frame->ApplyGlobalSettings(false);
7633 return (true);
7634 }
7635 }
7636#endif
7637
7638 // Update modifiers here; some window managers never send the key event
7639 m_modkeys = 0;
7640 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7641 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7642
7643#ifdef __WXMSW__
7644 // TODO Test carefully in other platforms, remove ifdef....
7645 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7646 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7647#endif
7648
7649 event.SetEventObject(this);
7650 if (SendMouseEventToPlugins(event))
7651 return (true); // PlugIn did something, and does not want the canvas to
7652 // do anything else
7653
7654 // Capture LeftUp's and time them, unless it already came from the timer.
7655
7656 // Detect end of chart dragging
7657 if (g_btouch && m_bChartDragging && event.LeftUp()) {
7658 StartChartDragInertia();
7659 }
7660
7661 if (b_handle_dclick && event.LeftUp() && !singleClickEventIsValid) {
7662 // Ignore the second LeftUp after the DClick.
7663 if (m_DoubleClickTimer->IsRunning()) {
7664 m_DoubleClickTimer->Stop();
7665 return (true);
7666 }
7667
7668 // Save the event for later running if there is no DClick.
7669 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7670 singleClickEvent = event;
7671 singleClickEventIsValid = true;
7672 return (true);
7673 }
7674
7675 // This logic is necessary on MSW to handle the case where
7676 // a context (right-click) menu is dismissed without action
7677 // by clicking on the chart surface.
7678 // We need to avoid an unintentional pan by eating some clicks...
7679#ifdef __WXMSW__
7680 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7681 if (g_click_stop > 0) {
7682 g_click_stop--;
7683 return (true);
7684 }
7685 }
7686#endif
7687
7688 // Kick off the Rotation control timer
7689 if (GetUpMode() == COURSE_UP_MODE) {
7690 m_b_rot_hidef = false;
7691 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7692 } else
7693 pRotDefTimer->Stop();
7694
7695 // Retrigger the route leg / AIS target popup timer
7696 bool bRoll = !g_btouch;
7697#ifdef __ANDROID__
7698 bRoll = g_bRollover;
7699#endif
7700 if (bRoll) {
7701 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7702 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7703 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7704 m_RolloverPopupTimer.Start(
7705 10,
7706 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7707 else
7708 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7709 }
7710
7711 // Retrigger the cursor tracking timer
7712 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7713
7714// Show cursor position on Status Bar, if present
7715// except for GTK, under which status bar updates are very slow
7716// due to Update() call.
7717// In this case, as a workaround, update the status window
7718// after an interval timer (pCurTrackTimer) pops, which will happen
7719// whenever the mouse has stopped moving for specified interval.
7720// See the method OnCursorTrackTimerEvent()
7721#if !defined(__WXGTK__) && !defined(__WXQT__)
7722 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7723#endif
7724
7725 // Send the current cursor lat/lon to all PlugIns requesting it
7726 if (g_pi_manager) {
7727 // Occasionally, MSW will produce nonsense events on right click....
7728 // This results in an error in cursor geo position, so we skip this case
7729 if ((x >= 0) && (y >= 0))
7730 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7731 }
7732
7733 if (!g_btouch) {
7734 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7735 wxPoint p = ClientToScreen(wxPoint(x, y));
7736 }
7737 }
7738
7739 if (1 ) {
7740 // Route Creation Rubber Banding
7741 if (m_routeState >= 2) {
7742 r_rband.x = x;
7743 r_rband.y = y;
7744 m_bDrawingRoute = true;
7745
7746 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7747 Refresh(false);
7748 }
7749
7750 // Measure Tool Rubber Banding
7751 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7752 r_rband.x = x;
7753 r_rband.y = y;
7754 m_bDrawingRoute = true;
7755
7756 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7757 Refresh(false);
7758 }
7759 }
7760 return bret;
7761}
7762
7763void ChartCanvas::CallPopupMenu(int x, int y) {
7764 int mx, my;
7765 mx = x;
7766 my = y;
7767
7768 last_drag.x = mx;
7769 last_drag.y = my;
7770 if (m_routeState) { // creating route?
7771 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
7772 return;
7773 }
7774 // General Right Click
7775 // Look for selectable objects
7776 double slat, slon;
7777 slat = m_cursor_lat;
7778 slon = m_cursor_lon;
7779
7780#if defined(__WXMAC__) || defined(__ANDROID__)
7781 wxScreenDC sdc;
7782 ocpnDC dc(sdc);
7783#else
7784 wxClientDC cdc(GetParent());
7785 ocpnDC dc(cdc);
7786#endif
7787
7788 SelectItem *pFindAIS;
7789 SelectItem *pFindRP;
7790 SelectItem *pFindRouteSeg;
7791 SelectItem *pFindTrackSeg;
7792 SelectItem *pFindCurrent = NULL;
7793 SelectItem *pFindTide = NULL;
7794
7795 // Deselect any current objects
7796 if (m_pSelectedRoute) {
7797 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7798 m_pSelectedRoute->DeSelectRoute();
7799#ifdef ocpnUSE_GL
7800 if (g_bopengl && m_glcc) {
7801 InvalidateGL();
7802 Update();
7803 } else
7804#endif
7805 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7806 }
7807
7808 if (m_pFoundRoutePoint) {
7809 m_pFoundRoutePoint->m_bPtIsSelected = false;
7810 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
7811 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
7812 }
7813
7816 if (g_btouch && m_pRoutePointEditTarget) {
7817 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7818 m_pRoutePointEditTarget->m_bPtIsSelected = false;
7819 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7820 }
7821
7822 // Get all the selectable things at the cursor
7823 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7824 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7825 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7826 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7827 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7828
7829 if (m_bShowCurrent)
7830 pFindCurrent =
7831 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7832
7833 if (m_bShowTide) // look for tide stations
7834 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7835
7836 int seltype = 0;
7837
7838 // Try for AIS targets first
7839 if (pFindAIS) {
7840 m_FoundAIS_MMSI = pFindAIS->GetUserData();
7841
7842 // Make sure the target data is available
7843 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
7844 seltype |= SELTYPE_AISTARGET;
7845 }
7846
7847 // Now the various Route Parts
7848
7849 m_pFoundRoutePoint = NULL;
7850 if (pFindRP) {
7851 RoutePoint *pFirstVizPoint = NULL;
7852 RoutePoint *pFoundActiveRoutePoint = NULL;
7853 RoutePoint *pFoundVizRoutePoint = NULL;
7854 Route *pSelectedActiveRoute = NULL;
7855 Route *pSelectedVizRoute = NULL;
7856
7857 // There is at least one routepoint, so get the whole list
7858 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7859 SelectableItemList SelList =
7860 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7861 wxSelectableItemListNode *node = SelList.GetFirst();
7862 while (node) {
7863 SelectItem *pFindSel = node->GetData();
7864
7865 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7866
7867 // Get an array of all routes using this point
7868 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7869
7870 // Use route array (if any) to determine actual visibility for this point
7871 bool brp_viz = false;
7872 if (proute_array) {
7873 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7874 Route *pr = (Route *)proute_array->Item(ir);
7875 if (pr->IsVisible()) {
7876 brp_viz = true;
7877 break;
7878 }
7879 }
7880 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7881 // but still exists as a waypoint
7882 brp_viz = prp->IsVisible(); // so treat as isolated point
7883
7884 } else
7885 brp_viz = prp->IsVisible(); // isolated point
7886
7887 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7888
7889 // Use route array to choose the appropriate route
7890 // Give preference to any active route, otherwise select the first visible
7891 // route in the array for this point
7892 m_pSelectedRoute = NULL;
7893 if (proute_array) {
7894 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7895 Route *pr = (Route *)proute_array->Item(ir);
7896 if (pr->m_bRtIsActive) {
7897 pSelectedActiveRoute = pr;
7898 pFoundActiveRoutePoint = prp;
7899 break;
7900 }
7901 }
7902
7903 if (NULL == pSelectedVizRoute) {
7904 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7905 Route *pr = (Route *)proute_array->Item(ir);
7906 if (pr->IsVisible()) {
7907 pSelectedVizRoute = pr;
7908 pFoundVizRoutePoint = prp;
7909 break;
7910 }
7911 }
7912 }
7913
7914 delete proute_array;
7915 }
7916
7917 node = node->GetNext();
7918 }
7919
7920 // Now choose the "best" selections
7921 if (pFoundActiveRoutePoint) {
7922 m_pFoundRoutePoint = pFoundActiveRoutePoint;
7923 m_pSelectedRoute = pSelectedActiveRoute;
7924 } else if (pFoundVizRoutePoint) {
7925 m_pFoundRoutePoint = pFoundVizRoutePoint;
7926 m_pSelectedRoute = pSelectedVizRoute;
7927 } else
7928 // default is first visible point in list
7929 m_pFoundRoutePoint = pFirstVizPoint;
7930
7931 if (m_pSelectedRoute) {
7932 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7933 } else if (m_pFoundRoutePoint)
7934 seltype |= SELTYPE_MARKPOINT;
7935
7936 // Highlite the selected point, to verify the proper right click
7937 // selection
7938 if (m_pFoundRoutePoint) {
7939 m_pFoundRoutePoint->m_bPtIsSelected = true;
7940 wxRect wp_rect;
7941 RoutePointGui(*m_pFoundRoutePoint)
7942 .CalculateDCRect(m_dc_route, this, &wp_rect);
7943 RefreshRect(wp_rect, true);
7944 }
7945 }
7946
7947 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7948 // routes But call the popup handler with identifier appropriate to the type
7949 if (pFindRouteSeg) // there is at least one select item
7950 {
7951 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7952 SelectableItemList SelList =
7953 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7954
7955 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
7956 {
7957 // Choose the first visible route containing segment in the list
7958 wxSelectableItemListNode *node = SelList.GetFirst();
7959 while (node) {
7960 SelectItem *pFindSel = node->GetData();
7961
7962 Route *pr = (Route *)pFindSel->m_pData3;
7963 if (pr->IsVisible()) {
7964 m_pSelectedRoute = pr;
7965 break;
7966 }
7967 node = node->GetNext();
7968 }
7969 }
7970
7971 if (m_pSelectedRoute) {
7972 if (NULL == m_pFoundRoutePoint)
7973 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7974
7975 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7976 if (m_pSelectedRoute->m_bRtIsSelected) {
7977#ifdef ocpnUSE_GL
7978 if (g_bopengl && m_glcc) {
7979 InvalidateGL();
7980 Update();
7981 } else
7982#endif
7983 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7984 }
7985
7986 seltype |= SELTYPE_ROUTESEGMENT;
7987 }
7988 }
7989
7990 if (pFindTrackSeg) {
7991 m_pSelectedTrack = NULL;
7992 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7993 SelectableItemList SelList =
7994 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7995
7996 // Choose the first visible track containing segment in the list
7997 wxSelectableItemListNode *node = SelList.GetFirst();
7998 while (node) {
7999 SelectItem *pFindSel = node->GetData();
8000
8001 Track *pt = (Track *)pFindSel->m_pData3;
8002 if (pt->IsVisible()) {
8003 m_pSelectedTrack = pt;
8004 break;
8005 }
8006 node = node->GetNext();
8007 }
8008
8009 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8010 }
8011
8012 bool bseltc = false;
8013 // if(0 == seltype)
8014 {
8015 if (pFindCurrent) {
8016 // There may be multiple current entries at the same point.
8017 // For example, there often is a current substation (with directions
8018 // specified) co-located with its master. We want to select the
8019 // substation, so that the direction will be properly indicated on the
8020 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8021 // substation)
8022 IDX_entry *pIDX_best_candidate;
8023
8024 SelectItem *pFind = NULL;
8025 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8026 SelectableItemList SelList = pSelectTC->FindSelectionList(
8027 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_CURRENTPOINT);
8028
8029 // Default is first entry
8030 wxSelectableItemListNode *node = SelList.GetFirst();
8031 pFind = node->GetData();
8032 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8033
8034 if (SelList.GetCount() > 1) {
8035 node = node->GetNext();
8036 while (node) {
8037 pFind = node->GetData();
8038 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8039 if (pIDX_candidate->IDX_type == 'c') {
8040 pIDX_best_candidate = pIDX_candidate;
8041 break;
8042 }
8043
8044 node = node->GetNext();
8045 } // while (node)
8046 } else {
8047 wxSelectableItemListNode *node = SelList.GetFirst();
8048 pFind = node->GetData();
8049 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8050 }
8051
8052 m_pIDXCandidate = pIDX_best_candidate;
8053
8054 if (0 == seltype) {
8055 DrawTCWindow(x, y, (void *)pIDX_best_candidate);
8056 Refresh(false);
8057 bseltc = true;
8058 } else
8059 seltype |= SELTYPE_CURRENTPOINT;
8060 }
8061
8062 else if (pFindTide) {
8063 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8064
8065 if (0 == seltype) {
8066 DrawTCWindow(x, y, (void *)pFindTide->m_pData1);
8067 Refresh(false);
8068 bseltc = true;
8069 } else
8070 seltype |= SELTYPE_TIDEPOINT;
8071 }
8072 }
8073
8074 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8075
8076 if (!bseltc) {
8077 InvokeCanvasMenu(x, y, seltype);
8078
8079 // Clean up if not deleted in InvokeCanvasMenu
8080 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8081 m_pSelectedRoute->m_bRtIsSelected = false;
8082 }
8083
8084 m_pSelectedRoute = NULL;
8085
8086 if (m_pFoundRoutePoint) {
8087 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8088 m_pFoundRoutePoint->m_bPtIsSelected = false;
8089 }
8090 m_pFoundRoutePoint = NULL;
8091
8092 Refresh(true);
8093 }
8094
8095 // Seth: Is this refresh needed?
8096 Refresh(false); // needed for MSW, not GTK Why??
8097}
8098bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8099 // For now just bail out completely if the point clicked is not on the chart
8100 if (std::isnan(m_cursor_lat)) return false;
8101
8102 // Mouse Clicks
8103 bool ret = false; // return true if processed
8104
8105 int x, y, mx, my;
8106 event.GetPosition(&x, &y);
8107 mx = x;
8108 my = y;
8109
8110 // Calculate meaningful SelectRadius
8111 float SelectRadius;
8112 SelectRadius = g_Platform->GetSelectRadiusPix() /
8113 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8114
8116 // We start with Double Click processing. The first left click just starts a
8117 // timer and is remembered, then we actually do something if there is a
8118 // LeftDClick. If there is, the two single clicks are ignored.
8119
8120 if (event.LeftDClick() && (cursor_region == CENTER)) {
8121 m_DoubleClickTimer->Start();
8122 singleClickEventIsValid = false;
8123
8124 double zlat, zlon;
8125 GetCanvasPixPoint(x * g_current_monitor_dip_px_ratio,
8126 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8127
8128 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8129 if (m_bShowAIS) {
8130 SelectItem *pFindAIS;
8131 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8132
8133 if (pFindAIS) {
8134 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8135 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8136 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8137 }
8138 return true;
8139 }
8140 }
8141
8142 SelectableItemList rpSelList =
8143 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8144 wxSelectableItemListNode *node = rpSelList.GetFirst();
8145 bool b_onRPtarget = false;
8146 while (node) {
8147 SelectItem *pFind = node->GetData();
8148 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8149 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8150 b_onRPtarget = true;
8151 break;
8152 }
8153 node = node->GetNext();
8154 }
8155
8156 // Double tap with selected RoutePoint or Mark
8157
8158 if (m_pRoutePointEditTarget) {
8159 if (b_onRPtarget) {
8160 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8161 return true;
8162 } else {
8163 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8164 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8165 if (g_btouch)
8166 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8167 wxRect wp_rect;
8168 RoutePointGui(*m_pRoutePointEditTarget)
8169 .CalculateDCRect(m_dc_route, this, &wp_rect);
8170 m_pRoutePointEditTarget = NULL; // cancel selection
8171 RefreshRect(wp_rect, true);
8172 return true;
8173 }
8174 } else {
8175 node = rpSelList.GetFirst();
8176 if (node) {
8177 SelectItem *pFind = node->GetData();
8178 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8179 if (frp) {
8180 wxArrayPtrVoid *proute_array =
8181 g_pRouteMan->GetRouteArrayContaining(frp);
8182
8183 // Use route array (if any) to determine actual visibility for this
8184 // point
8185 bool brp_viz = false;
8186 if (proute_array) {
8187 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8188 Route *pr = (Route *)proute_array->Item(ir);
8189 if (pr->IsVisible()) {
8190 brp_viz = true;
8191 break;
8192 }
8193 }
8194 delete proute_array;
8195 if (!brp_viz &&
8196 frp->IsShared()) // is not visible as part of route, but still
8197 // exists as a waypoint
8198 brp_viz = frp->IsVisible(); // so treat as isolated point
8199 } else
8200 brp_viz = frp->IsVisible(); // isolated point
8201
8202 if (brp_viz) {
8203 ShowMarkPropertiesDialog(frp);
8204 return true;
8205 }
8206 }
8207 }
8208 }
8209
8210 SelectItem *cursorItem;
8211 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8212
8213 if (cursorItem) {
8214 Route *pr = (Route *)cursorItem->m_pData3;
8215 if (pr->IsVisible()) {
8216 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8217 return true;
8218 }
8219 }
8220
8221 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8222
8223 if (cursorItem) {
8224 Track *pt = (Track *)cursorItem->m_pData3;
8225 if (pt->IsVisible()) {
8226 ShowTrackPropertiesDialog(pt);
8227 return true;
8228 }
8229 }
8230
8231 // Found no object to act on, so show chart info.
8232
8233 ShowObjectQueryWindow(x, y, zlat, zlon);
8234 return true;
8235 }
8236
8238 if (event.LeftDown()) {
8239 // This really should not be needed, but....
8240 // on Windows, when using wxAUIManager, sometimes the focus is lost
8241 // when clicking into another pane, e.g.the AIS target list, and then back
8242 // to this pane. Oddly, some mouse events are not lost, however. Like this
8243 // one....
8244 SetFocus();
8245
8246 last_drag.x = mx;
8247 last_drag.y = my;
8248 leftIsDown = true;
8249
8250 if (!g_btouch) {
8251 if (m_routeState) // creating route?
8252 {
8253 double rlat, rlon;
8254 bool appending = false;
8255 bool inserting = false;
8256 Route *tail = 0;
8257
8258 SetCursor(*pCursorPencil);
8259 rlat = m_cursor_lat;
8260 rlon = m_cursor_lon;
8261
8262 m_bRouteEditing = true;
8263
8264 if (m_routeState == 1) {
8265 m_pMouseRoute = new Route();
8266 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8267 pRouteList->Append(m_pMouseRoute);
8268 r_rband.x = x;
8269 r_rband.y = y;
8270 }
8271
8272 // Check to see if there is a nearby point which may be reused
8273 RoutePoint *pMousePoint = NULL;
8274
8275 // Calculate meaningful SelectRadius
8276 double nearby_radius_meters =
8277 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8278
8279 RoutePoint *pNearbyPoint =
8280 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8281 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8282 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8283 wxArrayPtrVoid *proute_array =
8284 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8285
8286 // Use route array (if any) to determine actual visibility for this
8287 // point
8288 bool brp_viz = false;
8289 if (proute_array) {
8290 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8291 Route *pr = (Route *)proute_array->Item(ir);
8292 if (pr->IsVisible()) {
8293 brp_viz = true;
8294 break;
8295 }
8296 }
8297 delete proute_array;
8298 if (!brp_viz &&
8299 pNearbyPoint->IsShared()) // is not visible as part of route,
8300 // but still exists as a waypoint
8301 brp_viz =
8302 pNearbyPoint->IsVisible(); // so treat as isolated point
8303 } else
8304 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8305
8306 if (brp_viz) {
8307 wxString msg = _("Use nearby waypoint?");
8308 // Don't add a mark without name to the route. Name it if needed
8309 const bool noname(pNearbyPoint->GetName() == "");
8310 if (noname) {
8311 msg =
8312 _("Use nearby nameless waypoint and name it M with"
8313 " a unique number?");
8314 }
8315 // Avoid route finish on focus change for message dialog
8316 m_FinishRouteOnKillFocus = false;
8317 int dlg_return =
8318 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8319 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8320 m_FinishRouteOnKillFocus = true;
8321 if (dlg_return == wxID_YES) {
8322 if (noname) {
8323 if (m_pMouseRoute) {
8324 int last_wp_num = m_pMouseRoute->GetnPoints();
8325 // AP-ECRMB will truncate to 6 characters
8326 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8327 wxString wp_name = wxString::Format(
8328 "M%002i-%s", last_wp_num + 1, guid_short);
8329 pNearbyPoint->SetName(wp_name);
8330 } else
8331 pNearbyPoint->SetName("WPXX");
8332 }
8333 pMousePoint = pNearbyPoint;
8334
8335 // Using existing waypoint, so nothing to delete for undo.
8336 if (m_routeState > 1)
8337 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8338 Undo_HasParent, NULL);
8339
8340 tail =
8341 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8342 bool procede = false;
8343 if (tail) {
8344 procede = true;
8345 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8346 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8347 procede = false;
8348 }
8349
8350 if (procede) {
8351 int dlg_return;
8352 m_FinishRouteOnKillFocus = false;
8353 if (m_routeState ==
8354 1) { // first point in new route, preceeding route to be
8355 // added? Not touch case
8356
8357 wxString dmsg =
8358 _("Insert first part of this route in the new route?");
8359 if (tail->GetIndexOf(pMousePoint) ==
8360 tail->GetnPoints()) // Starting on last point of another
8361 // route?
8362 dmsg = _("Insert this route in the new route?");
8363
8364 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8365 dlg_return = OCPNMessageBox(
8366 this, dmsg, _("OpenCPN Route Create"),
8367 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8368 m_FinishRouteOnKillFocus = true;
8369
8370 if (dlg_return == wxID_YES) {
8371 inserting = true; // part of the other route will be
8372 // preceeding the new route
8373 }
8374 }
8375 } else {
8376 wxString dmsg =
8377 _("Append last part of this route to the new route?");
8378 if (tail->GetIndexOf(pMousePoint) == 1)
8379 dmsg = _(
8380 "Append this route to the new route?"); // Picking the
8381 // first point
8382 // of another
8383 // route?
8384
8385 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8386 dlg_return = OCPNMessageBox(
8387 this, dmsg, _("OpenCPN Route Create"),
8388 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8389 m_FinishRouteOnKillFocus = true;
8390
8391 if (dlg_return == wxID_YES) {
8392 appending = true; // part of the other route will be
8393 // appended to the new route
8394 }
8395 }
8396 }
8397 }
8398
8399 // check all other routes to see if this point appears in any
8400 // other route If it appears in NO other route, then it should e
8401 // considered an isolated mark
8402 if (!FindRouteContainingWaypoint(pMousePoint))
8403 pMousePoint->SetShared(true);
8404 }
8405 }
8406 }
8407
8408 if (NULL == pMousePoint) { // need a new point
8409 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8410 _T(""), wxEmptyString);
8411 pMousePoint->SetNameShown(false);
8412
8413 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8414
8415 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8416
8417 if (m_routeState > 1)
8418 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8419 Undo_IsOrphanded, NULL);
8420 }
8421
8422 if (m_pMouseRoute) {
8423 if (m_routeState == 1) {
8424 // First point in the route.
8425 m_pMouseRoute->AddPoint(pMousePoint);
8426 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8427 } else {
8428 if (m_pMouseRoute->m_NextLegGreatCircle) {
8429 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8430 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8431 &rhumbBearing, &rhumbDist);
8432 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8433 rlat, &gcDist, &gcBearing, NULL);
8434 double gcDistNM = gcDist / 1852.0;
8435
8436 // Empirically found expression to get reasonable route segments.
8437 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8438 pow(rhumbDist - gcDistNM - 1, 0.5);
8439
8440 wxString msg;
8441 msg << _("For this leg the Great Circle route is ")
8442 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8443 << _(" shorter than rhumbline.\n\n")
8444 << _("Would you like include the Great Circle routing points "
8445 "for this leg?");
8446
8447 m_FinishRouteOnKillFocus = false;
8448 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8449 // does not fully capture mouse
8450
8451 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8452 wxYES_NO | wxNO_DEFAULT);
8453
8454 m_disable_edge_pan = false;
8455 m_FinishRouteOnKillFocus = true;
8456
8457 if (answer == wxID_YES) {
8458 RoutePoint *gcPoint;
8459 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8460 wxRealPoint gcCoord;
8461
8462 for (int i = 1; i <= segmentCount; i++) {
8463 double fraction = (double)i * (1.0 / (double)segmentCount);
8464 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8465 gcDist * fraction, gcBearing,
8466 &gcCoord.x, &gcCoord.y, NULL);
8467
8468 if (i < segmentCount) {
8469 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
8470 _T(""), wxEmptyString);
8471 gcPoint->SetNameShown(false);
8472 // pConfig->AddNewWayPoint(gcPoint, -1);
8473 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8474
8475 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8476 gcPoint);
8477 } else {
8478 gcPoint = pMousePoint; // Last point, previously exsisting!
8479 }
8480
8481 m_pMouseRoute->AddPoint(gcPoint);
8482 pSelect->AddSelectableRouteSegment(
8483 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8484 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8485 prevGcPoint = gcPoint;
8486 }
8487
8488 undo->CancelUndoableAction(true);
8489
8490 } else {
8491 m_pMouseRoute->AddPoint(pMousePoint);
8492 pSelect->AddSelectableRouteSegment(
8493 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8494 pMousePoint, m_pMouseRoute);
8495 undo->AfterUndoableAction(m_pMouseRoute);
8496 }
8497 } else {
8498 // Ordinary rhumblinesegment.
8499 m_pMouseRoute->AddPoint(pMousePoint);
8500 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8501 rlon, m_prev_pMousePoint,
8502 pMousePoint, m_pMouseRoute);
8503 undo->AfterUndoableAction(m_pMouseRoute);
8504 }
8505 }
8506 }
8507 m_prev_rlat = rlat;
8508 m_prev_rlon = rlon;
8509 m_prev_pMousePoint = pMousePoint;
8510 if (m_pMouseRoute)
8511 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8512
8513 m_routeState++;
8514
8515 if (appending ||
8516 inserting) { // Appending a route or making a new route
8517 int connect = tail->GetIndexOf(pMousePoint);
8518 if (connect == 1) {
8519 inserting = false; // there is nothing to insert
8520 appending = true; // so append
8521 }
8522 int length = tail->GetnPoints();
8523
8524 int i;
8525 int start, stop;
8526 if (appending) {
8527 start = connect + 1;
8528 stop = length;
8529 } else { // inserting
8530 start = 1;
8531 stop = connect;
8532 m_pMouseRoute->RemovePoint(
8533 m_pMouseRoute
8534 ->GetLastPoint()); // Remove the first and only point
8535 }
8536 for (i = start; i <= stop; i++) {
8537 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8538 if (m_pMouseRoute)
8539 m_pMouseRoute->m_lastMousePointIndex =
8540 m_pMouseRoute->GetnPoints();
8541 m_routeState++;
8542 gFrame->RefreshAllCanvas();
8543 ret = true;
8544 }
8545 m_prev_rlat =
8546 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8547 m_prev_rlon =
8548 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8549 m_pMouseRoute->FinalizeForRendering();
8550 }
8551 gFrame->RefreshAllCanvas();
8552 ret = true;
8553 }
8554
8555 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8556 {
8557 SetCursor(*pCursorPencil);
8558
8559 if (!m_pMeasureRoute) {
8560 m_pMeasureRoute = new Route();
8561 pRouteList->Append(m_pMeasureRoute);
8562 }
8563
8564 if (m_nMeasureState == 1) {
8565 r_rband.x = x;
8566 r_rband.y = y;
8567 }
8568
8569 RoutePoint *pMousePoint = new RoutePoint(m_cursor_lat, m_cursor_lon,
8570 wxString(_T ( "circle" )),
8571 wxEmptyString, wxEmptyString);
8572 pMousePoint->m_bShowName = false;
8573 pMousePoint->SetShowWaypointRangeRings(false);
8574
8575 m_pMeasureRoute->AddPoint(pMousePoint);
8576
8577 m_prev_rlat = m_cursor_lat;
8578 m_prev_rlon = m_cursor_lon;
8579 m_prev_pMousePoint = pMousePoint;
8580 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8581
8582 m_nMeasureState++;
8583 gFrame->RefreshAllCanvas();
8584 ret = true;
8585 }
8586
8587 else {
8588 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8589 }
8590 } // !g_btouch
8591 else { // g_btouch
8592
8593 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8594 // if near screen edge, pan with injection
8595 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8596 // return;
8597 // }
8598 }
8599 }
8600
8601 if (ret) return true;
8602 }
8603
8604 if (event.Dragging()) {
8605 // in touch screen mode ensure the finger/cursor is on the selected point's
8606 // radius to allow dragging
8607 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8608 if (g_btouch) {
8609 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8610 SelectItem *pFind = NULL;
8611 SelectableItemList SelList = pSelect->FindSelectionList(
8612 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8613 wxSelectableItemListNode *node = SelList.GetFirst();
8614 while (node) {
8615 pFind = node->GetData();
8616 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8617 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8618 node = node->GetNext();
8619 }
8620 }
8621
8622 // Check for use of dragHandle
8623 if (m_pRoutePointEditTarget &&
8624 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8625 SelectItem *pFind = NULL;
8626 SelectableItemList SelList = pSelect->FindSelectionList(
8627 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8628 wxSelectableItemListNode *node = SelList.GetFirst();
8629 while (node) {
8630 pFind = node->GetData();
8631 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8632 if (m_pRoutePointEditTarget == frp) {
8633 m_bIsInRadius = true;
8634 break;
8635 }
8636 node = node->GetNext();
8637 }
8638
8639 if (!m_dragoffsetSet) {
8640 RoutePointGui(*m_pRoutePointEditTarget)
8641 .PresetDragOffset(this, mouse_x, mouse_y);
8642 m_dragoffsetSet = true;
8643 }
8644 }
8645 }
8646
8647 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8648 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8649
8650 if (NULL == g_pMarkInfoDialog) {
8651 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8652 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8653 DraggingAllowed = false;
8654
8655 if (m_pRoutePointEditTarget &&
8656 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8657 DraggingAllowed = false;
8658
8659 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8660
8661 if (DraggingAllowed) {
8662 if (!undo->InUndoableAction()) {
8663 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8664 Undo_NeedsCopy, m_pFoundPoint);
8665 }
8666
8667 // Get the update rectangle for the union of the un-edited routes
8668 wxRect pre_rect;
8669
8670 if (!g_bopengl && m_pEditRouteArray) {
8671 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8672 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8673 // Need to validate route pointer
8674 // Route may be gone due to drgging close to ownship with
8675 // "Delete On Arrival" state set, as in the case of
8676 // navigating to an isolated waypoint on a temporary route
8677 if (g_pRouteMan->IsRouteValid(pr)) {
8678 wxRect route_rect;
8679 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8680 pre_rect.Union(route_rect);
8681 }
8682 }
8683 }
8684
8685 double new_cursor_lat = m_cursor_lat;
8686 double new_cursor_lon = m_cursor_lon;
8687
8688 if (CheckEdgePan(x, y, true, 5, 2))
8689 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8690
8691 // update the point itself
8692 if (g_btouch) {
8693 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8694 // new_cursor_lat, new_cursor_lon);
8695 RoutePointGui(*m_pRoutePointEditTarget)
8696 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8697 // update the Drag Handle entry in the pSelect list
8698 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8699 m_pRoutePointEditTarget,
8700 SELTYPE_DRAGHANDLE);
8701 m_pFoundPoint->m_slat =
8702 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8703 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8704 } else {
8705 m_pRoutePointEditTarget->m_lat =
8706 new_cursor_lat; // update the RoutePoint entry
8707 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8708 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8709 m_pFoundPoint->m_slat =
8710 new_cursor_lat; // update the SelectList entry
8711 m_pFoundPoint->m_slon = new_cursor_lon;
8712 }
8713
8714 // Update the MarkProperties Dialog, if currently shown
8715 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8716 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8717 g_pMarkInfoDialog->UpdateProperties(true);
8718 }
8719
8720 if (g_bopengl) {
8721 // InvalidateGL();
8722 Refresh(false);
8723 } else {
8724 // Get the update rectangle for the edited route
8725 wxRect post_rect;
8726
8727 if (m_pEditRouteArray) {
8728 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8729 ir++) {
8730 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8731 if (g_pRouteMan->IsRouteValid(pr)) {
8732 wxRect route_rect;
8733 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8734 post_rect.Union(route_rect);
8735 }
8736 }
8737 }
8738
8739 // Invalidate the union region
8740 pre_rect.Union(post_rect);
8741 RefreshRect(pre_rect, false);
8742 }
8743 gFrame->RefreshCanvasOther(this);
8744 m_bRoutePoinDragging = true;
8745 }
8746 ret = true;
8747 } // if Route Editing
8748
8749 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8750 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8751
8752 if (NULL == g_pMarkInfoDialog) {
8753 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8754 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8755 DraggingAllowed = false;
8756
8757 if (m_pRoutePointEditTarget &&
8758 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8759 DraggingAllowed = false;
8760
8761 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8762
8763 if (DraggingAllowed) {
8764 if (!undo->InUndoableAction()) {
8765 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8766 Undo_NeedsCopy, m_pFoundPoint);
8767 }
8768
8769 // The mark may be an anchorwatch
8770 double lpp1 = 0.;
8771 double lpp2 = 0.;
8772 double lppmax;
8773
8774 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8775 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8776 }
8777 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8778 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8779 }
8780 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8781
8782 // Get the update rectangle for the un-edited mark
8783 wxRect pre_rect;
8784 if (!g_bopengl) {
8785 RoutePointGui(*m_pRoutePointEditTarget)
8786 .CalculateDCRect(m_dc_route, this, &pre_rect);
8787 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8788 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8789 (int)(lppmax - (pre_rect.height / 2)));
8790 }
8791
8792 // update the point itself
8793 if (g_btouch) {
8794 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8795 // m_cursor_lat, m_cursor_lon);
8796 RoutePointGui(*m_pRoutePointEditTarget)
8797 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8798 // update the Drag Handle entry in the pSelect list
8799 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
8800 m_pRoutePointEditTarget,
8801 SELTYPE_DRAGHANDLE);
8802 m_pFoundPoint->m_slat =
8803 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8804 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8805 } else {
8806 m_pRoutePointEditTarget->m_lat =
8807 m_cursor_lat; // update the RoutePoint entry
8808 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
8809 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8810 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
8811 m_pFoundPoint->m_slon = m_cursor_lon;
8812 }
8813
8814 // Update the MarkProperties Dialog, if currently shown
8815 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8816 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8817 g_pMarkInfoDialog->UpdateProperties(true);
8818 }
8819
8820 // Invalidate the union region
8821 if (g_bopengl) {
8822 if (!g_btouch) InvalidateGL();
8823 Refresh(false);
8824 } else {
8825 // Get the update rectangle for the edited mark
8826 wxRect post_rect;
8827 RoutePointGui(*m_pRoutePointEditTarget)
8828 .CalculateDCRect(m_dc_route, this, &post_rect);
8829 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
8830 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
8831 (int)(lppmax - (post_rect.height / 2)));
8832
8833 // Invalidate the union region
8834 pre_rect.Union(post_rect);
8835 RefreshRect(pre_rect, false);
8836 }
8837 gFrame->RefreshCanvasOther(this);
8838 m_bRoutePoinDragging = true;
8839 }
8840 ret = true;
8841 }
8842
8843 if (ret) return true;
8844 } // dragging
8845
8846 if (event.LeftUp()) {
8847 bool b_startedit_route = false;
8848 m_dragoffsetSet = false;
8849
8850 if (g_btouch) {
8851 m_bChartDragging = false;
8852 m_bIsInRadius = false;
8853
8854 if (m_routeState) // creating route?
8855 {
8856 if (m_bedge_pan) {
8857 m_bedge_pan = false;
8858 return false;
8859 }
8860
8861 double rlat, rlon;
8862 bool appending = false;
8863 bool inserting = false;
8864 Route *tail = 0;
8865
8866 rlat = m_cursor_lat;
8867 rlon = m_cursor_lon;
8868
8869 if (m_pRoutePointEditTarget) {
8870 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8871 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8872 if (!g_bopengl) {
8873 wxRect wp_rect;
8874 RoutePointGui(*m_pRoutePointEditTarget)
8875 .CalculateDCRect(m_dc_route, this, &wp_rect);
8876 RefreshRect(wp_rect, true);
8877 }
8878 m_pRoutePointEditTarget = NULL;
8879 }
8880 m_bRouteEditing = true;
8881
8882 if (m_routeState == 1) {
8883 m_pMouseRoute = new Route();
8884 m_pMouseRoute->SetHiLite(50);
8885 pRouteList->Append(m_pMouseRoute);
8886 r_rband.x = x;
8887 r_rband.y = y;
8888 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8889 }
8890
8891 // Check to see if there is a nearby point which may be reused
8892 RoutePoint *pMousePoint = NULL;
8893
8894 // Calculate meaningful SelectRadius
8895 double nearby_radius_meters =
8896 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8897
8898 RoutePoint *pNearbyPoint =
8899 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8900 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8901 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8902 int dlg_return;
8903#ifndef __WXOSX__
8904 m_FinishRouteOnKillFocus =
8905 false; // Avoid route finish on focus change for message dialog
8906 dlg_return = OCPNMessageBox(
8907 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
8908 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8909 m_FinishRouteOnKillFocus = true;
8910#else
8911 dlg_return = wxID_YES;
8912#endif
8913 if (dlg_return == wxID_YES) {
8914 pMousePoint = pNearbyPoint;
8915
8916 // Using existing waypoint, so nothing to delete for undo.
8917 if (m_routeState > 1)
8918 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8919 Undo_HasParent, NULL);
8920 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8921
8922 bool procede = false;
8923 if (tail) {
8924 procede = true;
8925 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8926 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8927 procede = false;
8928 }
8929
8930 if (procede) {
8931 int dlg_return;
8932 m_FinishRouteOnKillFocus = false;
8933 if (m_routeState == 1) { // first point in new route, preceeding
8934 // route to be added? touch case
8935
8936 wxString dmsg =
8937 _("Insert first part of this route in the new route?");
8938 if (tail->GetIndexOf(pMousePoint) ==
8939 tail->GetnPoints()) // Starting on last point of another
8940 // route?
8941 dmsg = _("Insert this route in the new route?");
8942
8943 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8944 dlg_return =
8945 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8946 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8947 m_FinishRouteOnKillFocus = true;
8948
8949 if (dlg_return == wxID_YES) {
8950 inserting = true; // part of the other route will be
8951 // preceeding the new route
8952 }
8953 }
8954 } else {
8955 wxString dmsg =
8956 _("Append last part of this route to the new route?");
8957 if (tail->GetIndexOf(pMousePoint) == 1)
8958 dmsg = _(
8959 "Append this route to the new route?"); // Picking the
8960 // first point of
8961 // another route?
8962
8963 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8964 dlg_return =
8965 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8966 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8967 m_FinishRouteOnKillFocus = true;
8968
8969 if (dlg_return == wxID_YES) {
8970 appending = true; // part of the other route will be
8971 // appended to the new route
8972 }
8973 }
8974 }
8975 }
8976
8977 // check all other routes to see if this point appears in any other
8978 // route If it appears in NO other route, then it should e
8979 // considered an isolated mark
8980 if (!FindRouteContainingWaypoint(pMousePoint))
8981 pMousePoint->SetShared(true);
8982 }
8983 }
8984
8985 if (NULL == pMousePoint) { // need a new point
8986 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8987 _T(""), wxEmptyString);
8988 pMousePoint->SetNameShown(false);
8989
8990 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8991
8992 if (m_routeState > 1)
8993 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8994 Undo_IsOrphanded, NULL);
8995 }
8996
8997 if (m_routeState == 1) {
8998 // First point in the route.
8999 m_pMouseRoute->AddPoint(pMousePoint);
9000 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9001
9002 } else {
9003 if (m_pMouseRoute->m_NextLegGreatCircle) {
9004 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9005 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9006 &rhumbBearing, &rhumbDist);
9007 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9008 &gcDist, &gcBearing, NULL);
9009 double gcDistNM = gcDist / 1852.0;
9010
9011 // Empirically found expression to get reasonable route segments.
9012 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9013 pow(rhumbDist - gcDistNM - 1, 0.5);
9014
9015 wxString msg;
9016 msg << _("For this leg the Great Circle route is ")
9017 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9018 << _(" shorter than rhumbline.\n\n")
9019 << _("Would you like include the Great Circle routing points "
9020 "for this leg?");
9021
9022#ifndef __WXOSX__
9023 m_FinishRouteOnKillFocus = false;
9024 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9025 wxYES_NO | wxNO_DEFAULT);
9026 m_FinishRouteOnKillFocus = true;
9027#else
9028 int answer = wxID_NO;
9029#endif
9030
9031 if (answer == wxID_YES) {
9032 RoutePoint *gcPoint;
9033 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9034 wxRealPoint gcCoord;
9035
9036 for (int i = 1; i <= segmentCount; i++) {
9037 double fraction = (double)i * (1.0 / (double)segmentCount);
9038 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9039 gcDist * fraction, gcBearing,
9040 &gcCoord.x, &gcCoord.y, NULL);
9041
9042 if (i < segmentCount) {
9043 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
9044 _T(""), wxEmptyString);
9045 gcPoint->SetNameShown(false);
9046 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9047 gcPoint);
9048 } else {
9049 gcPoint = pMousePoint; // Last point, previously exsisting!
9050 }
9051
9052 m_pMouseRoute->AddPoint(gcPoint);
9053 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9054
9055 pSelect->AddSelectableRouteSegment(
9056 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9057 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9058 prevGcPoint = gcPoint;
9059 }
9060
9061 undo->CancelUndoableAction(true);
9062
9063 } else {
9064 m_pMouseRoute->AddPoint(pMousePoint);
9065 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9066 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9067 rlon, m_prev_pMousePoint,
9068 pMousePoint, m_pMouseRoute);
9069 undo->AfterUndoableAction(m_pMouseRoute);
9070 }
9071 } else {
9072 // Ordinary rhumblinesegment.
9073 m_pMouseRoute->AddPoint(pMousePoint);
9074 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9075
9076 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9077 rlon, m_prev_pMousePoint,
9078 pMousePoint, m_pMouseRoute);
9079 undo->AfterUndoableAction(m_pMouseRoute);
9080 }
9081 }
9082
9083 m_prev_rlat = rlat;
9084 m_prev_rlon = rlon;
9085 m_prev_pMousePoint = pMousePoint;
9086 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9087
9088 m_routeState++;
9089
9090 if (appending ||
9091 inserting) { // Appending a route or making a new route
9092 int connect = tail->GetIndexOf(pMousePoint);
9093 if (connect == 1) {
9094 inserting = false; // there is nothing to insert
9095 appending = true; // so append
9096 }
9097 int length = tail->GetnPoints();
9098
9099 int i;
9100 int start, stop;
9101 if (appending) {
9102 start = connect + 1;
9103 stop = length;
9104 } else { // inserting
9105 start = 1;
9106 stop = connect;
9107 m_pMouseRoute->RemovePoint(
9108 m_pMouseRoute
9109 ->GetLastPoint()); // Remove the first and only point
9110 }
9111 for (i = start; i <= stop; i++) {
9112 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9113 if (m_pMouseRoute)
9114 m_pMouseRoute->m_lastMousePointIndex =
9115 m_pMouseRoute->GetnPoints();
9116 m_routeState++;
9117 gFrame->RefreshAllCanvas();
9118 ret = true;
9119 }
9120 m_prev_rlat =
9121 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9122 m_prev_rlon =
9123 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9124 m_pMouseRoute->FinalizeForRendering();
9125 }
9126
9127 Refresh(true);
9128 ret = true;
9129 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9130 {
9131 if (m_bedge_pan) {
9132 m_bedge_pan = false;
9133 return false;
9134 }
9135
9136 if (m_nMeasureState == 1) {
9137 m_pMeasureRoute = new Route();
9138 pRouteList->Append(m_pMeasureRoute);
9139 r_rband.x = x;
9140 r_rband.y = y;
9141 }
9142
9143 if (m_pMeasureRoute) {
9144 RoutePoint *pMousePoint = new RoutePoint(
9145 m_cursor_lat, m_cursor_lon, wxString(_T ( "circle" )),
9146 wxEmptyString, wxEmptyString);
9147 pMousePoint->m_bShowName = false;
9148
9149 m_pMeasureRoute->AddPoint(pMousePoint);
9150
9151 m_prev_rlat = m_cursor_lat;
9152 m_prev_rlon = m_cursor_lon;
9153 m_prev_pMousePoint = pMousePoint;
9154 m_pMeasureRoute->m_lastMousePointIndex =
9155 m_pMeasureRoute->GetnPoints();
9156
9157 m_nMeasureState++;
9158 } else {
9159 CancelMeasureRoute();
9160 }
9161
9162 Refresh(true);
9163 ret = true;
9164 } else {
9165 bool bSelectAllowed = true;
9166 if (NULL == g_pMarkInfoDialog) {
9167 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9168 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9169 bSelectAllowed = false;
9170
9171 /*if this left up happens at the end of a route point dragging and if
9172 the cursor/thumb is on the draghandle icon, not on the point iself a new
9173 selection will select nothing and the drag will never be ended, so the
9174 legs around this point never selectable. At this step we don't need a
9175 new selection, just keep the previoulsly selected and dragged point */
9176 if (m_bRoutePoinDragging) bSelectAllowed = false;
9177
9178 if (bSelectAllowed) {
9179 bool b_was_editing_mark = m_bMarkEditing;
9180 bool b_was_editing_route = m_bRouteEditing;
9181 FindRoutePointsAtCursor(SelectRadius,
9182 true); // Possibly selecting a point in a
9183 // route for later dragging
9184
9185 /*route and a mark points in layer can't be dragged so should't be
9186 * selected and no draghandle icon*/
9187 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9188 m_pRoutePointEditTarget = NULL;
9189
9190 if (!b_was_editing_route) {
9191 if (m_pEditRouteArray) {
9192 b_startedit_route = true;
9193
9194 // Hide the track and route rollover during route point edit, not
9195 // needed, and may be confusing
9196 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9197 m_pTrackRolloverWin->IsActive(false);
9198 }
9199 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9200 m_pRouteRolloverWin->IsActive(false);
9201 }
9202
9203 wxRect pre_rect;
9204 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9205 ir++) {
9206 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9207 // Need to validate route pointer
9208 // Route may be gone due to drgging close to ownship with
9209 // "Delete On Arrival" state set, as in the case of
9210 // navigating to an isolated waypoint on a temporary route
9211 if (g_pRouteMan->IsRouteValid(pr)) {
9212 // pr->SetHiLite(50);
9213 wxRect route_rect;
9214 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9215 pre_rect.Union(route_rect);
9216 }
9217 }
9218 RefreshRect(pre_rect, true);
9219 }
9220 } else {
9221 b_startedit_route = false;
9222 }
9223
9224 // Mark editing
9225 if (m_pRoutePointEditTarget) {
9226 if (b_was_editing_mark ||
9227 b_was_editing_route) { // kill previous hilight
9228 if (m_lastRoutePointEditTarget) {
9229 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9230 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9231 RoutePointGui(*m_lastRoutePointEditTarget)
9232 .EnableDragHandle(false);
9233 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9234 SELTYPE_DRAGHANDLE);
9235 }
9236 }
9237
9238 if (m_pRoutePointEditTarget) {
9239 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9240 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9241 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9242 wxPoint2DDouble dragHandlePoint =
9243 RoutePointGui(*m_pRoutePointEditTarget)
9244 .GetDragHandlePoint(this);
9245 pSelect->AddSelectablePoint(
9246 dragHandlePoint.m_y, dragHandlePoint.m_x,
9247 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9248 }
9249 } else { // Deselect everything
9250 if (m_lastRoutePointEditTarget) {
9251 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9252 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9253 RoutePointGui(*m_lastRoutePointEditTarget)
9254 .EnableDragHandle(false);
9255 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9256 SELTYPE_DRAGHANDLE);
9257
9258 // Clear any routes being edited, probably orphans
9259 wxArrayPtrVoid *lastEditRouteArray =
9260 g_pRouteMan->GetRouteArrayContaining(
9261 m_lastRoutePointEditTarget);
9262 if (lastEditRouteArray) {
9263 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9264 ir++) {
9265 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9266 if (g_pRouteMan->IsRouteValid(pr)) {
9267 pr->m_bIsBeingEdited = false;
9268 }
9269 }
9270 delete lastEditRouteArray;
9271 }
9272 }
9273 }
9274
9275 // Do the refresh
9276
9277 if (g_bopengl) {
9278 InvalidateGL();
9279 Refresh(false);
9280 } else {
9281 if (m_lastRoutePointEditTarget) {
9282 wxRect wp_rect;
9283 RoutePointGui(*m_lastRoutePointEditTarget)
9284 .CalculateDCRect(m_dc_route, this, &wp_rect);
9285 RefreshRect(wp_rect, true);
9286 }
9287
9288 if (m_pRoutePointEditTarget) {
9289 wxRect wp_rect;
9290 RoutePointGui(*m_pRoutePointEditTarget)
9291 .CalculateDCRect(m_dc_route, this, &wp_rect);
9292 RefreshRect(wp_rect, true);
9293 }
9294 }
9295 }
9296 } // bSelectAllowed
9297
9298 // Check to see if there is a route or AIS target under the cursor
9299 // If so, start the rollover timer which creates the popup
9300 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9301 bool b_start_rollover = false;
9302 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9303 SelectItem *pFind = pSelectAIS->FindSelection(
9304 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9305 if (pFind) b_start_rollover = true;
9306 }
9307
9308 if (!b_start_rollover && !b_startedit_route) {
9309 SelectableItemList SelList = pSelect->FindSelectionList(
9310 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9311 wxSelectableItemListNode *node = SelList.GetFirst();
9312 while (node) {
9313 SelectItem *pFindSel = node->GetData();
9314
9315 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9316
9317 if (pr && pr->IsVisible()) {
9318 b_start_rollover = true;
9319 break;
9320 }
9321 node = node->GetNext();
9322 } // while
9323 }
9324
9325 if (!b_start_rollover && !b_startedit_route) {
9326 SelectableItemList SelList = pSelect->FindSelectionList(
9327 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9328 wxSelectableItemListNode *node = SelList.GetFirst();
9329 while (node) {
9330 SelectItem *pFindSel = node->GetData();
9331
9332 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9333
9334 if (tr && tr->IsVisible()) {
9335 b_start_rollover = true;
9336 break;
9337 }
9338 node = node->GetNext();
9339 } // while
9340 }
9341
9342 if (b_start_rollover)
9343 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9344 wxTIMER_ONE_SHOT);
9345 Route *tail = 0;
9346 Route *current = 0;
9347 bool appending = false;
9348 bool inserting = false;
9349 int connect = 0;
9350 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9351 // drag
9352 if (m_pRoutePointEditTarget) {
9353 // Check to see if there is a nearby point which may replace the
9354 // dragged one
9355 RoutePoint *pMousePoint = NULL;
9356
9357 int index_last;
9358 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9359 double nearby_radius_meters =
9360 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9361 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9362 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9363 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9364 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9365 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9366 bool duplicate =
9367 false; // ensure we won't create duplicate point in routes
9368 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9369 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9370 ir++) {
9371 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9372 if (pr && pr->pRoutePointList) {
9373 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9374 wxNOT_FOUND) {
9375 duplicate = true;
9376 break;
9377 }
9378 }
9379 }
9380 }
9381
9382 // Special case:
9383 // Allow "re-use" of a route's waypoints iff it is a simple
9384 // isolated route. This allows, for instance, creation of a closed
9385 // polygon route
9386 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9387
9388 if (!duplicate) {
9389 int dlg_return;
9390 dlg_return =
9391 OCPNMessageBox(this,
9392 _("Replace this RoutePoint by the nearby "
9393 "Waypoint?"),
9394 _("OpenCPN RoutePoint change"),
9395 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9396 if (dlg_return == wxID_YES) {
9397 /*double confirmation if the dragged point has been manually
9398 * created which can be important and could be deleted
9399 * unintentionally*/
9400
9401 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9402 pNearbyPoint);
9403 current =
9404 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9405
9406 if (tail && current && (tail != current)) {
9407 int dlg_return1;
9408 connect = tail->GetIndexOf(pNearbyPoint);
9409 int index_current_route =
9410 current->GetIndexOf(m_pRoutePointEditTarget);
9411 index_last = current->GetIndexOf(current->GetLastPoint());
9412 dlg_return1 = wxID_NO;
9413 if (index_last ==
9414 index_current_route) { // we are dragging the last
9415 // point of the route
9416 if (connect != tail->GetnPoints()) { // anything to do?
9417
9418 wxString dmsg(
9419 _("Last part of route to be appended to dragged "
9420 "route?"));
9421 if (connect == 1)
9422 dmsg =
9423 _("Full route to be appended to dragged route?");
9424
9425 dlg_return1 = OCPNMessageBox(
9426 this, dmsg, _("OpenCPN Route Create"),
9427 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9428 if (dlg_return1 == wxID_YES) {
9429 appending = true;
9430 }
9431 }
9432 } else if (index_current_route ==
9433 1) { // dragging the first point of the route
9434 if (connect != 1) { // anything to do?
9435
9436 wxString dmsg(
9437 _("First part of route to be inserted into dragged "
9438 "route?"));
9439 if (connect == tail->GetnPoints())
9440 dmsg = _(
9441 "Full route to be inserted into dragged route?");
9442
9443 dlg_return1 = OCPNMessageBox(
9444 this, dmsg, _("OpenCPN Route Create"),
9445 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9446 if (dlg_return1 == wxID_YES) {
9447 inserting = true;
9448 }
9449 }
9450 }
9451 }
9452
9453 if (m_pRoutePointEditTarget->IsShared()) {
9454 // dlg_return = wxID_NO;
9455 dlg_return = OCPNMessageBox(
9456 this,
9457 _("Do you really want to delete and replace this "
9458 "WayPoint") +
9459 _T("\n") + _("which has been created manually?"),
9460 ("OpenCPN RoutePoint warning"),
9461 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9462 }
9463 }
9464 if (dlg_return == wxID_YES) {
9465 pMousePoint = pNearbyPoint;
9466 if (pMousePoint->m_bIsolatedMark) {
9467 pMousePoint->SetShared(true);
9468 }
9469 pMousePoint->m_bIsolatedMark =
9470 false; // definitely no longer isolated
9471 pMousePoint->m_bIsInRoute = true;
9472 }
9473 }
9474 }
9475 }
9476 if (!pMousePoint)
9477 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9478
9479 if (m_pEditRouteArray) {
9480 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9481 ir++) {
9482 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9483 if (g_pRouteMan->IsRouteValid(pr)) {
9484 if (pMousePoint) { // remove the dragged point and insert the
9485 // nearby
9486 int nRP =
9487 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9488
9489 pSelect->DeleteAllSelectableRoutePoints(pr);
9490 pSelect->DeleteAllSelectableRouteSegments(pr);
9491
9492 pr->pRoutePointList->Insert(nRP, pMousePoint);
9493 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9494
9495 pSelect->AddAllSelectableRouteSegments(pr);
9496 pSelect->AddAllSelectableRoutePoints(pr);
9497 }
9498 pr->FinalizeForRendering();
9499 pr->UpdateSegmentDistances();
9500 if (m_bRoutePoinDragging) {
9501 // pConfig->UpdateRoute(pr);
9502 NavObj_dB::GetInstance().UpdateRoute(pr);
9503 }
9504 }
9505 }
9506 }
9507
9508 // Update the RouteProperties Dialog, if currently shown
9509 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9510 if (m_pEditRouteArray) {
9511 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9512 ir++) {
9513 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9514 if (g_pRouteMan->IsRouteValid(pr)) {
9515 if (pRoutePropDialog->GetRoute() == pr) {
9516 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9517 }
9518 /* cannot edit track points anyway
9519 else if ( ( NULL !=
9520 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9521 pTrackPropDialog->m_pTrack == pr ) {
9522 pTrackPropDialog->SetTrackAndUpdate(
9523 pr );
9524 }
9525 */
9526 }
9527 }
9528 }
9529 }
9530 if (pMousePoint) { // clear all about the dragged point
9531 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9532 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9533 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9534 // Hide mark properties dialog if open on the replaced point
9535 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9536 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9537 g_pMarkInfoDialog->Hide();
9538
9539 delete m_pRoutePointEditTarget;
9540 m_lastRoutePointEditTarget = NULL;
9541 m_pRoutePointEditTarget = NULL;
9542 undo->AfterUndoableAction(pMousePoint);
9543 undo->InvalidateUndo();
9544 }
9545 }
9546 }
9547
9548 else if (m_bMarkEditing) { // End of way point drag
9549 if (m_pRoutePointEditTarget)
9550 if (m_bRoutePoinDragging) {
9551 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9552 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9553 }
9554 }
9555
9556 if (m_pRoutePointEditTarget)
9557 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9558
9559 if (!m_pRoutePointEditTarget) {
9560 delete m_pEditRouteArray;
9561 m_pEditRouteArray = NULL;
9562 m_bRouteEditing = false;
9563 }
9564 m_bRoutePoinDragging = false;
9565
9566 if (appending) { // Appending to the route of which the last point is
9567 // dragged onto another route
9568
9569 // copy tail from connect until length to end of current after dragging
9570
9571 int length = tail->GetnPoints();
9572 for (int i = connect + 1; i <= length; i++) {
9573 current->AddPointAndSegment(tail->GetPoint(i), false);
9574 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9575 m_routeState++;
9576 gFrame->RefreshAllCanvas();
9577 ret = true;
9578 }
9579 current->FinalizeForRendering();
9580 current->m_bIsBeingEdited = false;
9581 FinishRoute();
9582 g_pRouteMan->DeleteRoute(tail);
9583 }
9584 if (inserting) {
9585 pSelect->DeleteAllSelectableRoutePoints(current);
9586 pSelect->DeleteAllSelectableRouteSegments(current);
9587 for (int i = 1; i < connect; i++) { // numbering in the tail route
9588 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9589 }
9590 pSelect->AddAllSelectableRouteSegments(current);
9591 pSelect->AddAllSelectableRoutePoints(current);
9592 current->FinalizeForRendering();
9593 current->m_bIsBeingEdited = false;
9594 g_pRouteMan->DeleteRoute(tail);
9595 }
9596
9597 // Update the RouteProperties Dialog, if currently shown
9598 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9599 if (m_pEditRouteArray) {
9600 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9601 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9602 if (g_pRouteMan->IsRouteValid(pr)) {
9603 if (pRoutePropDialog->GetRoute() == pr) {
9604 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9605 }
9606 }
9607 }
9608 }
9609 }
9610
9611 } // g_btouch
9612
9613 else { // !g_btouch
9614 if (m_bRouteEditing) { // End of RoutePoint drag
9615 Route *tail = 0;
9616 Route *current = 0;
9617 bool appending = false;
9618 bool inserting = false;
9619 int connect = 0;
9620 int index_last;
9621 if (m_pRoutePointEditTarget) {
9622 m_pRoutePointEditTarget->m_bBlink = false;
9623 // Check to see if there is a nearby point which may replace the
9624 // dragged one
9625 RoutePoint *pMousePoint = NULL;
9626 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9627 double nearby_radius_meters =
9628 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9629 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9630 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9631 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9632 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9633 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9634 bool duplicate = false; // don't create duplicate point in routes
9635 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9636 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9637 ir++) {
9638 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9639 if (pr && pr->pRoutePointList) {
9640 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9641 wxNOT_FOUND) {
9642 duplicate = true;
9643 break;
9644 }
9645 }
9646 }
9647 }
9648
9649 // Special case:
9650 // Allow "re-use" of a route's waypoints iff it is a simple
9651 // isolated route. This allows, for instance, creation of a closed
9652 // polygon route
9653 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9654
9655 if (!duplicate) {
9656 int dlg_return;
9657 dlg_return =
9658 OCPNMessageBox(this,
9659 _("Replace this RoutePoint by the nearby "
9660 "Waypoint?"),
9661 _("OpenCPN RoutePoint change"),
9662 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9663 if (dlg_return == wxID_YES) {
9664 /*double confirmation if the dragged point has been manually
9665 * created which can be important and could be deleted
9666 * unintentionally*/
9667 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9668 pNearbyPoint);
9669 current =
9670 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9671
9672 if (tail && current && (tail != current)) {
9673 int dlg_return1;
9674 connect = tail->GetIndexOf(pNearbyPoint);
9675 int index_current_route =
9676 current->GetIndexOf(m_pRoutePointEditTarget);
9677 index_last = current->GetIndexOf(current->GetLastPoint());
9678 dlg_return1 = wxID_NO;
9679 if (index_last ==
9680 index_current_route) { // we are dragging the last
9681 // point of the route
9682 if (connect != tail->GetnPoints()) { // anything to do?
9683
9684 wxString dmsg(
9685 _("Last part of route to be appended to dragged "
9686 "route?"));
9687 if (connect == 1)
9688 dmsg =
9689 _("Full route to be appended to dragged route?");
9690
9691 dlg_return1 = OCPNMessageBox(
9692 this, dmsg, _("OpenCPN Route Create"),
9693 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9694 if (dlg_return1 == wxID_YES) {
9695 appending = true;
9696 }
9697 }
9698 } else if (index_current_route ==
9699 1) { // dragging the first point of the route
9700 if (connect != 1) { // anything to do?
9701
9702 wxString dmsg(
9703 _("First part of route to be inserted into dragged "
9704 "route?"));
9705 if (connect == tail->GetnPoints())
9706 dmsg = _(
9707 "Full route to be inserted into dragged route?");
9708
9709 dlg_return1 = OCPNMessageBox(
9710 this, dmsg, _("OpenCPN Route Create"),
9711 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9712 if (dlg_return1 == wxID_YES) {
9713 inserting = true;
9714 }
9715 }
9716 }
9717 }
9718
9719 if (m_pRoutePointEditTarget->IsShared()) {
9720 dlg_return = wxID_NO;
9721 dlg_return = OCPNMessageBox(
9722 this,
9723 _("Do you really want to delete and replace this "
9724 "WayPoint") +
9725 _T("\n") + _("which has been created manually?"),
9726 ("OpenCPN RoutePoint warning"),
9727 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9728 }
9729 }
9730 if (dlg_return == wxID_YES) {
9731 pMousePoint = pNearbyPoint;
9732 if (pMousePoint->m_bIsolatedMark) {
9733 pMousePoint->SetShared(true);
9734 }
9735 pMousePoint->m_bIsolatedMark =
9736 false; // definitely no longer isolated
9737 pMousePoint->m_bIsInRoute = true;
9738 }
9739 }
9740 }
9741 }
9742 if (!pMousePoint)
9743 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9744
9745 if (m_pEditRouteArray) {
9746 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9747 ir++) {
9748 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9749 if (g_pRouteMan->IsRouteValid(pr)) {
9750 if (pMousePoint) { // replace dragged point by nearby one
9751 int nRP =
9752 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9753
9754 pSelect->DeleteAllSelectableRoutePoints(pr);
9755 pSelect->DeleteAllSelectableRouteSegments(pr);
9756
9757 pr->pRoutePointList->Insert(nRP, pMousePoint);
9758 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9759
9760 pSelect->AddAllSelectableRouteSegments(pr);
9761 pSelect->AddAllSelectableRoutePoints(pr);
9762 }
9763 pr->FinalizeForRendering();
9764 pr->UpdateSegmentDistances();
9765 pr->m_bIsBeingEdited = false;
9766
9767 if (m_bRoutePoinDragging) {
9768 // Special case optimization.
9769 // Dragging a single point of a route
9770 // without any point additions or re-ordering
9771 if (!pMousePoint)
9772 NavObj_dB::GetInstance().UpdateRoutePoint(
9773 m_pRoutePointEditTarget);
9774 else
9775 NavObj_dB::GetInstance().UpdateRoute(pr);
9776 }
9777 pr->SetHiLite(0);
9778 }
9779 }
9780 Refresh(false);
9781 }
9782
9783 if (appending) {
9784 // copy tail from connect until length to end of current after
9785 // dragging
9786
9787 int length = tail->GetnPoints();
9788 for (int i = connect + 1; i <= length; i++) {
9789 current->AddPointAndSegment(tail->GetPoint(i), false);
9790 if (current)
9791 current->m_lastMousePointIndex = current->GetnPoints();
9792 m_routeState++;
9793 gFrame->RefreshAllCanvas();
9794 ret = true;
9795 }
9796 current->FinalizeForRendering();
9797 current->m_bIsBeingEdited = false;
9798 FinishRoute();
9799 g_pRouteMan->DeleteRoute(tail);
9800 }
9801 if (inserting) {
9802 pSelect->DeleteAllSelectableRoutePoints(current);
9803 pSelect->DeleteAllSelectableRouteSegments(current);
9804 for (int i = 1; i < connect; i++) { // numbering in the tail route
9805 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9806 }
9807 pSelect->AddAllSelectableRouteSegments(current);
9808 pSelect->AddAllSelectableRoutePoints(current);
9809 current->FinalizeForRendering();
9810 current->m_bIsBeingEdited = false;
9811 g_pRouteMan->DeleteRoute(tail);
9812 }
9813
9814 // Update the RouteProperties Dialog, if currently shown
9815 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9816 if (m_pEditRouteArray) {
9817 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9818 ir++) {
9819 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9820 if (g_pRouteMan->IsRouteValid(pr)) {
9821 if (pRoutePropDialog->GetRoute() == pr) {
9822 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9823 }
9824 }
9825 }
9826 }
9827 }
9828
9829 if (pMousePoint) {
9830 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9831 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9832 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9833 // Hide mark properties dialog if open on the replaced point
9834 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9835 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9836 g_pMarkInfoDialog->Hide();
9837
9838 delete m_pRoutePointEditTarget;
9839 m_lastRoutePointEditTarget = NULL;
9840 undo->AfterUndoableAction(pMousePoint);
9841 undo->InvalidateUndo();
9842 } else {
9843 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9844 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9845
9846 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9847 }
9848
9849 delete m_pEditRouteArray;
9850 m_pEditRouteArray = NULL;
9851 }
9852
9853 InvalidateGL();
9854 m_bRouteEditing = false;
9855 m_pRoutePointEditTarget = NULL;
9856
9857 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9858 ret = true;
9859 }
9860
9861 else if (m_bMarkEditing) { // end of Waypoint drag
9862 if (m_pRoutePointEditTarget) {
9863 if (m_bRoutePoinDragging) {
9864 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9865 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9866 }
9867 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9868 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9869 if (!g_bopengl) {
9870 wxRect wp_rect;
9871 RoutePointGui(*m_pRoutePointEditTarget)
9872 .CalculateDCRect(m_dc_route, this, &wp_rect);
9873 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9874 RefreshRect(wp_rect, true);
9875 }
9876 }
9877 m_pRoutePointEditTarget = NULL;
9878 m_bMarkEditing = false;
9879 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9880 ret = true;
9881 }
9882
9883 else if (leftIsDown) { // left click for chart center
9884 leftIsDown = false;
9885 ret = false;
9886
9887 if (!g_btouch) {
9888 if (!m_bChartDragging && !m_bMeasure_Active) {
9889 } else {
9890 m_bChartDragging = false;
9891 }
9892 }
9893 }
9894 m_bRoutePoinDragging = false;
9895 } // !btouch
9896
9897 if (ret) return true;
9898 } // left up
9899
9900 if (event.RightDown()) {
9901 SetFocus(); // This is to let a plugin know which canvas is right-clicked
9902 last_drag.x = mx;
9903 last_drag.y = my;
9904
9905 if (g_btouch) {
9906 // if( m_pRoutePointEditTarget )
9907 // return false;
9908 }
9909
9910 ret = true;
9911 m_FinishRouteOnKillFocus = false;
9912 CallPopupMenu(mx, my);
9913 m_FinishRouteOnKillFocus = true;
9914 } // Right down
9915
9916 return ret;
9917}
9918
9919bool panleftIsDown;
9920
9921bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
9922 // Skip all mouse processing if shift is held.
9923 // This allows plugins to implement shift+drag behaviors.
9924 if (event.ShiftDown()) {
9925 return false;
9926 }
9927 int x, y;
9928 event.GetPosition(&x, &y);
9929
9930 x *= m_displayScale;
9931 y *= m_displayScale;
9932
9933 // Check for wheel rotation
9934 // ideally, should be just longer than the time between
9935 // processing accumulated mouse events from the event queue
9936 // as would happen during screen redraws.
9937 int wheel_dir = event.GetWheelRotation();
9938
9939 if (wheel_dir) {
9940 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
9941 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
9942
9943 double factor = g_mouse_zoom_sensitivity;
9944 if (wheel_dir < 0) factor = 1 / factor;
9945
9946 if (g_bsmoothpanzoom) {
9947 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
9948 if (wheel_dir == m_last_wheel_dir) {
9949 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
9950 // m_zoom_target /= factor;
9951 } else
9952 StopMovement();
9953 } else {
9954 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
9955 m_wheelstopwatch.Start(0);
9956 // m_zoom_target = VPoint.chart_scale / factor;
9957 }
9958 }
9959
9960 m_last_wheel_dir = wheel_dir;
9961
9962 ZoomCanvas(factor, true, false);
9963 }
9964
9965 if (event.LeftDown()) {
9966 // Skip the first left click if it will cause a canvas focus shift
9967 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
9968 // printf("focus shift\n");
9969 return false;
9970 }
9971
9972 last_drag.x = x, last_drag.y = y;
9973 panleftIsDown = true;
9974 }
9975
9976 if (event.LeftUp()) {
9977 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
9978 // seen here.
9979 panleftIsDown = false;
9980
9981 if (!g_btouch) {
9982 if (!m_bChartDragging && !m_bMeasure_Active) {
9983 switch (cursor_region) {
9984 case MID_RIGHT: {
9985 PanCanvas(100, 0);
9986 break;
9987 }
9988
9989 case MID_LEFT: {
9990 PanCanvas(-100, 0);
9991 break;
9992 }
9993
9994 case MID_TOP: {
9995 PanCanvas(0, 100);
9996 break;
9997 }
9998
9999 case MID_BOT: {
10000 PanCanvas(0, -100);
10001 break;
10002 }
10003
10004 case CENTER: {
10005 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10006 break;
10007 }
10008 }
10009 } else {
10010 m_bChartDragging = false;
10011 }
10012 }
10013 }
10014 }
10015
10016 if (event.Dragging() && event.LeftIsDown()) {
10017 /*
10018 * fixed dragging.
10019 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10020 * before the drag event. Hence, as there is no mouse down event, last_drag
10021 * is not reset before the drag. And that results in one single drag
10022 * session, meaning you cannot drag the map a few miles north, lift your
10023 * finger, and the go even further north. Instead, the map resets itself
10024 * always to the very first drag start (since there is not reset of
10025 * last_drag).
10026 *
10027 * Besides, should not left down and dragging be enough of a situation to
10028 * start a drag procedure?
10029 *
10030 * Anyways, guarded it to be active in touch situations only.
10031 */
10032
10033 if (g_btouch) {
10034 struct timespec now;
10035 clock_gettime(CLOCK_MONOTONIC, &now);
10036 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10037
10038 if (false == m_bChartDragging) {
10039 // Reset drag calculation members
10040 last_drag.x = x, last_drag.y = y;
10041 m_bChartDragging = true;
10042 m_chart_drag_total_time = 0;
10043 m_chart_drag_total_x = 0;
10044 m_chart_drag_total_y = 0;
10045 m_inertia_last_drag_x = x;
10046 m_inertia_last_drag_y = y;
10047 m_drag_vec_x.clear();
10048 m_drag_vec_y.clear();
10049 m_drag_vec_t.clear();
10050 m_last_drag_time = tnow;
10051 }
10052
10053 // Calculate and store drag dynamics.
10054 uint64_t delta_t = tnow - m_last_drag_time;
10055 double delta_tf = delta_t / 1e9;
10056
10057 m_chart_drag_total_time += delta_tf;
10058 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10059 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10060
10061 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10062 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10063 m_drag_vec_t.push_back(delta_tf);
10064
10065 m_inertia_last_drag_x = x;
10066 m_inertia_last_drag_y = y;
10067 m_last_drag_time = tnow;
10068
10069 if ((last_drag.x != x) || (last_drag.y != y)) {
10070 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10071 // dragging on route create.
10072 // github #2994
10073 m_bChartDragging = true;
10074 StartTimedMovement();
10075 m_pan_drag.x += last_drag.x - x;
10076 m_pan_drag.y += last_drag.y - y;
10077 last_drag.x = x, last_drag.y = y;
10078 }
10079 }
10080 } else {
10081 if ((last_drag.x != x) || (last_drag.y != y)) {
10082 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10083 // dragging on route create.
10084 // github #2994
10085 m_bChartDragging = true;
10086 StartTimedMovement();
10087 m_pan_drag.x += last_drag.x - x;
10088 m_pan_drag.y += last_drag.y - y;
10089 last_drag.x = x, last_drag.y = y;
10090 }
10091 }
10092 }
10093
10094 // Handle some special cases
10095 if (g_btouch) {
10096 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10097 // deactivate next LeftUp to ovoid creating an unexpected point
10098 m_DoubleClickTimer->Start();
10099 singleClickEventIsValid = false;
10100 }
10101 }
10102 }
10103
10104 return true;
10105}
10106
10107void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10108 if (MouseEventOverlayWindows(event)) return;
10109
10110 if (MouseEventSetup(event)) return; // handled, no further action required
10111
10112 if (!MouseEventProcessObjects(event)) MouseEventProcessCanvas(event);
10113}
10114
10115void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10116 // Switch to the appropriate cursor on mouse movement
10117
10118 wxCursor *ptarget_cursor = pCursorArrow;
10119 if (!pPlugIn_Cursor) {
10120 ptarget_cursor = pCursorArrow;
10121 if ((!m_routeState) &&
10122 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10123 if (cursor_region == MID_RIGHT) {
10124 ptarget_cursor = pCursorRight;
10125 } else if (cursor_region == MID_LEFT) {
10126 ptarget_cursor = pCursorLeft;
10127 } else if (cursor_region == MID_TOP) {
10128 ptarget_cursor = pCursorDown;
10129 } else if (cursor_region == MID_BOT) {
10130 ptarget_cursor = pCursorUp;
10131 } else {
10132 ptarget_cursor = pCursorArrow;
10133 }
10134 } else if (m_bMeasure_Active ||
10135 m_routeState) // If Measure tool use Pencil Cursor
10136 ptarget_cursor = pCursorPencil;
10137 } else {
10138 ptarget_cursor = pPlugIn_Cursor;
10139 }
10140
10141 SetCursor(*ptarget_cursor);
10142}
10143
10144void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10145 SetCursor(*pCursorArrow);
10146}
10147
10148void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10149 ChartPlugInWrapper *target_plugin_chart = NULL;
10150 s57chart *Chs57 = NULL;
10151 wxFileName file;
10152 wxArrayString files;
10153
10154 ChartBase *target_chart = GetChartAtCursor();
10155 if (target_chart) {
10156 file.Assign(target_chart->GetFullPath());
10157 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10158 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10159 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10160 else
10161 Chs57 = dynamic_cast<s57chart *>(target_chart);
10162 } else { // target_chart = null, might be mbtiles
10163 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10164 unsigned int im = stackIndexArray.size();
10165 int scale = 2147483647; // max 32b integer
10166 if (VPoint.b_quilt && im > 0) {
10167 for (unsigned int is = 0; is < im; is++) {
10168 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10169 CHART_TYPE_MBTILES) {
10170 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10171 double lat, lon;
10172 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10173 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10174 .GetBBox()
10175 .Contains(lat, lon)) {
10176 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10177 scale) {
10178 scale =
10179 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10180 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10181 }
10182 }
10183 }
10184 }
10185 }
10186 }
10187
10188 std::vector<Ais8_001_22 *> area_notices;
10189
10190 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10191 float vp_scale = GetVPScale();
10192
10193 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10194 auto target_data = target.second;
10195 if (!target_data->area_notices.empty()) {
10196 for (auto &ani : target_data->area_notices) {
10197 Ais8_001_22 &area_notice = ani.second;
10198
10199 BoundingBox bbox;
10200
10201 for (Ais8_001_22_SubAreaList::iterator sa =
10202 area_notice.sub_areas.begin();
10203 sa != area_notice.sub_areas.end(); ++sa) {
10204 switch (sa->shape) {
10205 case AIS8_001_22_SHAPE_CIRCLE: {
10206 wxPoint target_point;
10207 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10208 bbox.Expand(target_point);
10209 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10210 break;
10211 }
10212 case AIS8_001_22_SHAPE_RECT: {
10213 wxPoint target_point;
10214 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10215 bbox.Expand(target_point);
10216 if (sa->e_dim_m > sa->n_dim_m)
10217 bbox.EnLarge(sa->e_dim_m * vp_scale);
10218 else
10219 bbox.EnLarge(sa->n_dim_m * vp_scale);
10220 break;
10221 }
10222 case AIS8_001_22_SHAPE_POLYGON:
10223 case AIS8_001_22_SHAPE_POLYLINE: {
10224 for (int i = 0; i < 4; ++i) {
10225 double lat = sa->latitude;
10226 double lon = sa->longitude;
10227 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10228 &lat, &lon);
10229 wxPoint target_point;
10230 GetCanvasPointPix(lat, lon, &target_point);
10231 bbox.Expand(target_point);
10232 }
10233 break;
10234 }
10235 case AIS8_001_22_SHAPE_SECTOR: {
10236 double lat1 = sa->latitude;
10237 double lon1 = sa->longitude;
10238 double lat, lon;
10239 wxPoint target_point;
10240 GetCanvasPointPix(lat1, lon1, &target_point);
10241 bbox.Expand(target_point);
10242 for (int i = 0; i < 18; ++i) {
10243 ll_gc_ll(
10244 lat1, lon1,
10245 sa->left_bound_deg +
10246 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10247 sa->radius_m / 1852.0, &lat, &lon);
10248 GetCanvasPointPix(lat, lon, &target_point);
10249 bbox.Expand(target_point);
10250 }
10251 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10252 &lat, &lon);
10253 GetCanvasPointPix(lat, lon, &target_point);
10254 bbox.Expand(target_point);
10255 break;
10256 }
10257 }
10258 }
10259
10260 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10261 area_notices.push_back(&area_notice);
10262 }
10263 }
10264 }
10265 }
10266 }
10267
10268 if (target_chart || !area_notices.empty() || file.HasName()) {
10269 // Go get the array of all objects at the cursor lat/lon
10270 int sel_rad_pix = 5;
10271 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10272
10273 // Make sure we always get the lights from an object, even if we are
10274 // currently not displaying lights on the chart.
10275
10276 SetCursor(wxCURSOR_WAIT);
10277 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10278 if (!lightsVis) SetShowENCLights(true);
10279 ;
10280
10281 ListOfObjRazRules *rule_list = NULL;
10282 ListOfPI_S57Obj *pi_rule_list = NULL;
10283 if (Chs57)
10284 rule_list =
10285 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10286 else if (target_plugin_chart)
10287 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10288 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10289
10290 ListOfObjRazRules *overlay_rule_list = NULL;
10291 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10292 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10293
10294 if (CHs57_Overlay) {
10295 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10296 zlat, zlon, SelectRadius, &GetVP());
10297 }
10298
10299 if (!lightsVis) SetShowENCLights(false);
10300
10301 wxString objText;
10302 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10303 wxString face = dFont->GetFaceName();
10304
10305 if (NULL == g_pObjectQueryDialog) {
10306 g_pObjectQueryDialog =
10307 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10308 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10309 }
10310
10311 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10312 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10313
10314#ifdef __WXOSX__
10315 // Auto Adjustment for dark mode
10316 fg = g_pObjectQueryDialog->GetForegroundColour();
10317#endif
10318
10319 objText.Printf(
10320 _T("<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>"),
10321 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10322
10323#ifdef __WXOSX__
10324 int points = dFont->GetPointSize();
10325#else
10326 int points = dFont->GetPointSize() + 1;
10327#endif
10328
10329 int sizes[7];
10330 for (int i = -2; i < 5; i++) {
10331 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10332 }
10333 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10334
10335 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += _T("<i>");
10336
10337 if (overlay_rule_list && CHs57_Overlay) {
10338 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10339 objText << _T("<hr noshade>");
10340 }
10341
10342 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10343 an != area_notices.end(); ++an) {
10344 objText << _T( "<b>AIS Area Notice:</b> " );
10345 objText << ais8_001_22_notice_names[(*an)->notice_type];
10346 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10347 (*an)->sub_areas.begin();
10348 sa != (*an)->sub_areas.end(); ++sa)
10349 if (!sa->text.empty()) objText << sa->text;
10350 objText << _T( "<br>expires: " ) << (*an)->expiry_time.Format();
10351 objText << _T( "<hr noshade>" );
10352 }
10353
10354 if (Chs57)
10355 objText << Chs57->CreateObjDescriptions(rule_list);
10356 else if (target_plugin_chart)
10357 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10358 pi_rule_list);
10359
10360 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << _T("</i>");
10361
10362 // Add the additional info files
10363 wxString AddFiles, filenameOK;
10364 int filecount = 0;
10365 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10366 // plugin
10367
10368 AddFiles = wxString::Format(
10369 _T("<hr noshade><br><b>Additional info files attached to: </b> ")
10370 _T("<font ")
10371 _T("size=-2>%s</font><br><table border=0 cellspacing=0 ")
10372 _T("cellpadding=3>"),
10373 file.GetFullName());
10374 file.Normalize();
10375 file.Assign(file.GetPath(), wxT(""));
10376 wxDir dir(file.GetFullPath());
10377 wxString filename;
10378 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10379 while (cont) {
10380 file.Assign(dir.GetNameWithSep().append(filename));
10381 wxString FormatString =
10382 _T("<td valign=top><font size=-2><a ")
10383 _T("href=\"%s\">%s</a></font></td>");
10384 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10385 filenameOK = file.GetFullPath(); // remember last valid name
10386 // we are making a 3 columns table. New row only every third file
10387 if (3 * ((int)filecount / 3) == filecount)
10388 FormatString.Prepend(_T("<tr>")); // new row
10389 else
10390 FormatString.Prepend(
10391 _T("<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>")); // an empty
10392 // spacer column
10393
10394 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10395 file.GetFullName());
10396 filecount++;
10397 }
10398 cont = dir.GetNext(&filename);
10399 }
10400 objText << AddFiles << _T("</table>");
10401 }
10402 objText << _T("</font>");
10403 objText << _T("</body></html>");
10404
10405 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10406 g_pObjectQueryDialog->SetHTMLPage(objText);
10407 g_pObjectQueryDialog->Show();
10408 }
10409 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10410 // generate an event to avoid double code
10411 wxHtmlLinkInfo hli(filenameOK);
10412 wxHtmlLinkEvent hle(1, hli);
10413 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10414 }
10415
10416 if (rule_list) rule_list->Clear();
10417 delete rule_list;
10418
10419 if (overlay_rule_list) overlay_rule_list->Clear();
10420 delete overlay_rule_list;
10421
10422 if (pi_rule_list) pi_rule_list->Clear();
10423 delete pi_rule_list;
10424
10425 SetCursor(wxCURSOR_ARROW);
10426 }
10427}
10428
10429void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10430 bool bNew = false;
10431 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10432 // Dialog
10433 g_pMarkInfoDialog = new MarkInfoDlg(this);
10434 bNew = true;
10435 }
10436
10437 if (1 /*g_bresponsive*/) {
10438 wxSize canvas_size = GetSize();
10439
10440 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10441 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10442
10443 g_pMarkInfoDialog->Layout();
10444
10445 wxPoint canvas_pos = GetPosition();
10446 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10447
10448 bool newFit = false;
10449 if (canvas_size.x < fitted_size.x) {
10450 fitted_size.x = canvas_size.x - 40;
10451 if (canvas_size.y < fitted_size.y)
10452 fitted_size.y -= 40; // scrollbar added
10453 }
10454 if (canvas_size.y < fitted_size.y) {
10455 fitted_size.y = canvas_size.y - 40;
10456 if (canvas_size.x < fitted_size.x)
10457 fitted_size.x -= 40; // scrollbar added
10458 }
10459
10460 if (newFit) {
10461 g_pMarkInfoDialog->SetSize(fitted_size);
10462 g_pMarkInfoDialog->Centre();
10463 }
10464 }
10465
10466 markPoint->m_bRPIsBeingEdited = false;
10467
10468 wxString title_base = _("Mark Properties");
10469 if (markPoint->m_bIsInRoute) {
10470 title_base = _("Waypoint Properties");
10471 }
10472 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10473 g_pMarkInfoDialog->UpdateProperties();
10474 if (markPoint->m_bIsInLayer) {
10475 wxString caption(wxString::Format(_T("%s, %s: %s"), title_base, _("Layer"),
10476 GetLayerName(markPoint->m_LayerID)));
10477 g_pMarkInfoDialog->SetDialogTitle(caption);
10478 } else
10479 g_pMarkInfoDialog->SetDialogTitle(title_base);
10480
10481 g_pMarkInfoDialog->Show();
10482 g_pMarkInfoDialog->Raise();
10483 g_pMarkInfoDialog->InitialFocus();
10484 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10485}
10486
10487void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10488 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10489 pRoutePropDialog->SetRouteAndUpdate(selected);
10490 // pNew->UpdateProperties();
10491 pRoutePropDialog->Show();
10492 pRoutePropDialog->Raise();
10493 return;
10494 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10495 this); // There is one global instance of the RouteProp Dialog
10496
10497 if (g_bresponsive) {
10498 wxSize canvas_size = GetSize();
10499 wxPoint canvas_pos = GetPosition();
10500 wxSize fitted_size = pRoutePropDialog->GetSize();
10501 ;
10502
10503 if (canvas_size.x < fitted_size.x) {
10504 fitted_size.x = canvas_size.x;
10505 if (canvas_size.y < fitted_size.y)
10506 fitted_size.y -= 20; // scrollbar added
10507 }
10508 if (canvas_size.y < fitted_size.y) {
10509 fitted_size.y = canvas_size.y;
10510 if (canvas_size.x < fitted_size.x)
10511 fitted_size.x -= 20; // scrollbar added
10512 }
10513
10514 pRoutePropDialog->SetSize(fitted_size);
10515 pRoutePropDialog->Centre();
10516
10517 // int xp = (canvas_size.x - fitted_size.x)/2;
10518 // int yp = (canvas_size.y - fitted_size.y)/2;
10519
10520 wxPoint xxp = ClientToScreen(canvas_pos);
10521 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10522 }
10523
10524 pRoutePropDialog->SetRouteAndUpdate(selected);
10525
10526 pRoutePropDialog->Show();
10527
10528 Refresh(false);
10529}
10530
10531void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10532 pTrackPropDialog = TrackPropDlg::getInstance(
10533 this); // There is one global instance of the RouteProp Dialog
10534
10535 pTrackPropDialog->SetTrackAndUpdate(selected);
10536 pTrackPropDialog->UpdateProperties();
10537
10538 pTrackPropDialog->Show();
10539
10540 Refresh(false);
10541}
10542
10543void pupHandler_PasteWaypoint() {
10544 Kml kml;
10545
10546 int pasteBuffer = kml.ParsePasteBuffer();
10547 RoutePoint *pasted = kml.GetParsedRoutePoint();
10548 if (!pasted) return;
10549
10550 double nearby_radius_meters =
10551 g_Platform->GetSelectRadiusPix() /
10552 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10553
10554 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10555 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10556
10557 int answer = wxID_NO;
10558 if (nearPoint && !nearPoint->m_bIsInLayer) {
10559 wxString msg;
10560 msg << _(
10561 "There is an existing waypoint at the same location as the one you are "
10562 "pasting. Would you like to merge the pasted data with it?\n\n");
10563 msg << _("Answering 'No' will create a new waypoint at the same location.");
10564 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10565 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10566 }
10567
10568 if (answer == wxID_YES) {
10569 nearPoint->SetName(pasted->GetName());
10570 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10571 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10572 pRouteManagerDialog->UpdateWptListCtrl();
10573 }
10574
10575 if (answer == wxID_NO) {
10576 RoutePoint *newPoint = new RoutePoint(pasted);
10577 newPoint->m_bIsolatedMark = true;
10578 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10579 newPoint);
10580 // pConfig->AddNewWayPoint(newPoint, -1);
10581 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10582
10583 pWayPointMan->AddRoutePoint(newPoint);
10584 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10585 pRouteManagerDialog->UpdateWptListCtrl();
10586 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10587 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10588 }
10589
10590 gFrame->InvalidateAllGL();
10591 gFrame->RefreshAllCanvas(false);
10592}
10593
10594void pupHandler_PasteRoute() {
10595 Kml kml;
10596
10597 int pasteBuffer = kml.ParsePasteBuffer();
10598 Route *pasted = kml.GetParsedRoute();
10599 if (!pasted) return;
10600
10601 double nearby_radius_meters =
10602 g_Platform->GetSelectRadiusPix() /
10603 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10604
10605 RoutePoint *curPoint;
10606 RoutePoint *nearPoint;
10607 RoutePoint *prevPoint = NULL;
10608
10609 bool mergepoints = false;
10610 bool createNewRoute = true;
10611 int existingWaypointCounter = 0;
10612
10613 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10614 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10615 nearPoint = pWayPointMan->GetNearbyWaypoint(
10616 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10617 if (nearPoint) {
10618 mergepoints = true;
10619 existingWaypointCounter++;
10620 // Small hack here to avoid both extending RoutePoint and repeating all
10621 // the GetNearbyWaypoint calculations. Use existin data field in
10622 // RoutePoint as temporary storage.
10623 curPoint->m_bPtIsSelected = true;
10624 }
10625 }
10626
10627 int answer = wxID_NO;
10628 if (mergepoints) {
10629 wxString msg;
10630 msg << _(
10631 "There are existing waypoints at the same location as some of the ones "
10632 "you are pasting. Would you like to just merge the pasted data into "
10633 "them?\n\n");
10634 msg << _("Answering 'No' will create all new waypoints for this route.");
10635 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10636 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10637
10638 if (answer == wxID_CANCEL) {
10639 return;
10640 }
10641 }
10642
10643 // If all waypoints exist since before, and a route with the same name, we
10644 // don't create a new route.
10645 if (mergepoints && answer == wxID_YES &&
10646 existingWaypointCounter == pasted->GetnPoints()) {
10647 wxRouteListNode *route_node = pRouteList->GetFirst();
10648 while (route_node) {
10649 Route *proute = route_node->GetData();
10650
10651 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10652 createNewRoute = false;
10653 break;
10654 }
10655 route_node = route_node->GetNext();
10656 }
10657 }
10658
10659 Route *newRoute = 0;
10660 RoutePoint *newPoint = 0;
10661
10662 if (createNewRoute) {
10663 newRoute = new Route();
10664 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10665 }
10666
10667 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10668 curPoint = pasted->GetPoint(i);
10669 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10670 curPoint->m_bPtIsSelected = false;
10671 newPoint = pWayPointMan->GetNearbyWaypoint(
10672 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10673 newPoint->SetName(curPoint->GetName());
10674 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10675
10676 if (createNewRoute) newRoute->AddPoint(newPoint);
10677 } else {
10678 curPoint->m_bPtIsSelected = false;
10679
10680 newPoint = new RoutePoint(curPoint);
10681 newPoint->m_bIsolatedMark = false;
10682 newPoint->SetIconName(_T("circle"));
10683 newPoint->m_bIsVisible = true;
10684 newPoint->m_bShowName = false;
10685 newPoint->SetShared(false);
10686
10687 newRoute->AddPoint(newPoint);
10688 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10689 newPoint);
10690 // pConfig->AddNewWayPoint(newPoint, -1);
10691 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10692 pWayPointMan->AddRoutePoint(newPoint);
10693 }
10694 if (i > 1 && createNewRoute)
10695 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10696 curPoint->m_lat, curPoint->m_lon,
10697 prevPoint, newPoint, newRoute);
10698 prevPoint = newPoint;
10699 }
10700
10701 if (createNewRoute) {
10702 pRouteList->Append(newRoute);
10703 // pConfig->AddNewRoute(newRoute); // use auto next num
10704 NavObj_dB::GetInstance().InsertRoute(newRoute);
10705
10706 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10707 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10708 }
10709
10710 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10711 pRouteManagerDialog->UpdateRouteListCtrl();
10712 pRouteManagerDialog->UpdateWptListCtrl();
10713 }
10714 gFrame->InvalidateAllGL();
10715 gFrame->RefreshAllCanvas(false);
10716 }
10717 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10718 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10719}
10720
10721void pupHandler_PasteTrack() {
10722 Kml kml;
10723
10724 int pasteBuffer = kml.ParsePasteBuffer();
10725 Track *pasted = kml.GetParsedTrack();
10726 if (!pasted) return;
10727
10728 TrackPoint *curPoint;
10729
10730 Track *newTrack = new Track();
10731 TrackPoint *newPoint;
10732 TrackPoint *prevPoint = NULL;
10733
10734 newTrack->SetName(pasted->GetName());
10735
10736 for (int i = 0; i < pasted->GetnPoints(); i++) {
10737 curPoint = pasted->GetPoint(i);
10738
10739 newPoint = new TrackPoint(curPoint);
10740
10741 wxDateTime now = wxDateTime::Now();
10742 newPoint->SetCreateTime(curPoint->GetCreateTime());
10743
10744 newTrack->AddPoint(newPoint);
10745
10746 if (prevPoint)
10747 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10748 newPoint->m_lat, newPoint->m_lon,
10749 prevPoint, newPoint, newTrack);
10750
10751 prevPoint = newPoint;
10752 }
10753
10754 g_TrackList.push_back(newTrack);
10755 // pConfig->AddNewTrack(newTrack);
10756 NavObj_dB::GetInstance().InsertTrack(newTrack);
10757
10758 gFrame->InvalidateAllGL();
10759 gFrame->RefreshAllCanvas(false);
10760}
10761
10762bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
10763 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
10764 m_pFoundRoutePoint, m_FoundAIS_MMSI,
10765 m_pIDXCandidate, m_nmea_log);
10766
10767 Connect(
10768 wxEVT_COMMAND_MENU_SELECTED,
10769 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10770
10771 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
10772
10773 Disconnect(
10774 wxEVT_COMMAND_MENU_SELECTED,
10775 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10776
10777 delete m_canvasMenu;
10778 m_canvasMenu = NULL;
10779
10780#ifdef __WXQT__
10781 // gFrame->SurfaceToolbar();
10782 // g_MainToolbar->Raise();
10783#endif
10784
10785 return true;
10786}
10787
10788void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
10789 // Pass menu events from the canvas to the menu handler
10790 // This is necessarily in ChartCanvas since that is the menu's parent.
10791 if (m_canvasMenu) {
10792 m_canvasMenu->PopupMenuHandler(event);
10793 }
10794 return;
10795}
10796
10797void ChartCanvas::StartRoute(void) {
10798 // Do not allow more than one canvas to create a route at one time.
10799 if (g_brouteCreating) return;
10800
10801 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
10802
10803 g_brouteCreating = true;
10804 m_routeState = 1;
10805 m_bDrawingRoute = false;
10806 SetCursor(*pCursorPencil);
10807 // SetCanvasToolbarItemState(ID_ROUTE, true);
10808 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
10809
10810 HideGlobalToolbar();
10811
10812#ifdef __ANDROID__
10813 androidSetRouteAnnunciator(true);
10814#endif
10815}
10816
10817void ChartCanvas::FinishRoute(void) {
10818 m_routeState = 0;
10819 m_prev_pMousePoint = NULL;
10820 m_bDrawingRoute = false;
10821
10822 // SetCanvasToolbarItemState(ID_ROUTE, false);
10823 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
10824#ifdef __ANDROID__
10825 androidSetRouteAnnunciator(false);
10826#endif
10827
10828 SetCursor(*pCursorArrow);
10829
10830 if (m_pMouseRoute) {
10831 if (m_bAppendingRoute) {
10832 // pConfig->UpdateRoute(m_pMouseRoute);
10833 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
10834 } else {
10835 if (m_pMouseRoute->GetnPoints() > 1) {
10836 // pConfig->AddNewRoute(m_pMouseRoute);
10837 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
10838 } else {
10839 g_pRouteMan->DeleteRoute(m_pMouseRoute);
10840 m_pMouseRoute = NULL;
10841 }
10842 }
10843 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
10844
10845 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
10846 (pRoutePropDialog->IsShown())) {
10847 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
10848 }
10849
10850 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
10851 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10852 pRouteManagerDialog->UpdateRouteListCtrl();
10853 }
10854 }
10855 m_bAppendingRoute = false;
10856 m_pMouseRoute = NULL;
10857
10858 m_pSelectedRoute = NULL;
10859
10860 undo->InvalidateUndo();
10861 gFrame->RefreshAllCanvas(true);
10862
10863 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
10864
10865 ShowGlobalToolbar();
10866
10867 g_brouteCreating = false;
10868}
10869
10870void ChartCanvas::HideGlobalToolbar() {
10871 if (m_canvasIndex == 0) {
10872 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
10873 }
10874}
10875
10876void ChartCanvas::ShowGlobalToolbar() {
10877 if (m_canvasIndex == 0) {
10878 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
10879 }
10880}
10881
10882void ChartCanvas::ShowAISTargetList(void) {
10883 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
10884 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
10885 }
10886
10887 g_pAISTargetList->UpdateAISTargetList();
10888}
10889
10890void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
10891 if (!m_bShowOutlines) return;
10892
10893 if (!ChartData) return;
10894
10895 int nEntry = ChartData->GetChartTableEntries();
10896
10897 for (int i = 0; i < nEntry; i++) {
10898 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
10899
10900 // Check to see if the candidate chart is in the currently active group
10901 bool b_group_draw = false;
10902 if (m_groupIndex > 0) {
10903 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
10904 int index = pt->GetGroupArray()[ig];
10905 if (m_groupIndex == index) {
10906 b_group_draw = true;
10907 break;
10908 }
10909 }
10910 } else
10911 b_group_draw = true;
10912
10913 if (b_group_draw) RenderChartOutline(dc, i, vp);
10914 }
10915
10916 // On CM93 Composite Charts, draw the outlines of the next smaller
10917 // scale cell
10918 cm93compchart *pcm93 = NULL;
10919 if (VPoint.b_quilt) {
10920 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
10921 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
10922 pcm93 = (cm93compchart *)pch;
10923 break;
10924 }
10925 } else if (m_singleChart &&
10926 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
10927 pcm93 = (cm93compchart *)m_singleChart;
10928
10929 if (pcm93) {
10930 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
10931 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
10932
10933 if (zoom_factor > 8.0) {
10934 wxPen mPen(GetGlobalColor(_T("UINFM")), 2, wxPENSTYLE_SHORT_DASH);
10935 dc.SetPen(mPen);
10936 } else {
10937 wxPen mPen(GetGlobalColor(_T("UINFM")), 1, wxPENSTYLE_SOLID);
10938 dc.SetPen(mPen);
10939 }
10940
10941 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
10942 }
10943}
10944
10945void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
10946#ifdef ocpnUSE_GL
10947 if (g_bopengl && m_glcc) {
10948 /* opengl version specially optimized */
10949 m_glcc->RenderChartOutline(dc, dbIndex, vp);
10950 return;
10951 }
10952#endif
10953
10954 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
10955 if (!ChartData->IsChartAvailable(dbIndex)) return;
10956 }
10957
10958 float plylat, plylon;
10959 float plylat1, plylon1;
10960
10961 int pixx, pixy, pixx1, pixy1;
10962
10963 LLBBox box;
10964 ChartData->GetDBBoundingBox(dbIndex, box);
10965
10966 // Don't draw an outline in the case where the chart covers the entire world
10967 // */
10968 if (box.GetLonRange() == 360) return;
10969
10970 double lon_bias = 0;
10971 // chart is outside of viewport lat/lon bounding box
10972 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
10973
10974 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10975
10976 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
10977 dc.SetPen(wxPen(GetGlobalColor(_T ( "YELO1" )), 1, wxPENSTYLE_SOLID));
10978
10979 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
10980 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFG" )), 1, wxPENSTYLE_SOLID));
10981
10982 else
10983 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFR" )), 1, wxPENSTYLE_SOLID));
10984
10985 // Are there any aux ply entries?
10986 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
10987 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
10988 {
10989 wxPoint r, r1;
10990
10991 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10992 plylon += lon_bias;
10993
10994 GetCanvasPointPix(plylat, plylon, &r);
10995 pixx = r.x;
10996 pixy = r.y;
10997
10998 for (int i = 0; i < nPly - 1; i++) {
10999 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11000 plylon1 += lon_bias;
11001
11002 GetCanvasPointPix(plylat1, plylon1, &r1);
11003 pixx1 = r1.x;
11004 pixy1 = r1.y;
11005
11006 int pixxs1 = pixx1;
11007 int pixys1 = pixy1;
11008
11009 bool b_skip = false;
11010
11011 if (vp.chart_scale > 5e7) {
11012 // calculate projected distance between these two points in meters
11013 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11014 pow((double)(pixy1 - pixy), 2)) /
11015 vp.view_scale_ppm;
11016
11017 if (dist > 0.0) {
11018 // calculate GC distance between these two points in meters
11019 double distgc =
11020 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11021
11022 // If the distances are nonsense, it means that the scale is very
11023 // small and the segment wrapped the world So skip it....
11024 // TODO improve this to draw two segments
11025 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11026 b_skip = true;
11027 } else
11028 b_skip = true;
11029 }
11030
11031 ClipResult res = cohen_sutherland_line_clip_i(
11032 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11033 if (res != Invisible && !b_skip)
11034 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11035
11036 plylat = plylat1;
11037 plylon = plylon1;
11038 pixx = pixxs1;
11039 pixy = pixys1;
11040 }
11041
11042 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11043 plylon1 += lon_bias;
11044
11045 GetCanvasPointPix(plylat1, plylon1, &r1);
11046 pixx1 = r1.x;
11047 pixy1 = r1.y;
11048
11049 ClipResult res = cohen_sutherland_line_clip_i(
11050 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11051 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11052 }
11053
11054 else // Use Aux PlyPoints
11055 {
11056 wxPoint r, r1;
11057
11058 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11059 for (int j = 0; j < nAuxPlyEntries; j++) {
11060 int nAuxPly =
11061 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11062 GetCanvasPointPix(plylat, plylon, &r);
11063 pixx = r.x;
11064 pixy = r.y;
11065
11066 for (int i = 0; i < nAuxPly - 1; i++) {
11067 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11068
11069 GetCanvasPointPix(plylat1, plylon1, &r1);
11070 pixx1 = r1.x;
11071 pixy1 = r1.y;
11072
11073 int pixxs1 = pixx1;
11074 int pixys1 = pixy1;
11075
11076 bool b_skip = false;
11077
11078 if (vp.chart_scale > 5e7) {
11079 // calculate projected distance between these two points in meters
11080 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11081 ((pixy1 - pixy) * (pixy1 - pixy))) /
11082 vp.view_scale_ppm;
11083 if (dist > 0.0) {
11084 // calculate GC distance between these two points in meters
11085 double distgc =
11086 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11087
11088 // If the distances are nonsense, it means that the scale is very
11089 // small and the segment wrapped the world So skip it....
11090 // TODO improve this to draw two segments
11091 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11092 b_skip = true;
11093 } else
11094 b_skip = true;
11095 }
11096
11097 ClipResult res = cohen_sutherland_line_clip_i(
11098 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11099 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11100
11101 plylat = plylat1;
11102 plylon = plylon1;
11103 pixx = pixxs1;
11104 pixy = pixys1;
11105 }
11106
11107 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11108 GetCanvasPointPix(plylat1, plylon1, &r1);
11109 pixx1 = r1.x;
11110 pixy1 = r1.y;
11111
11112 ClipResult res = cohen_sutherland_line_clip_i(
11113 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11114 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11115 }
11116 }
11117}
11118
11119static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
11120 const wxString &second) {
11121 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11122
11123 int pointsize = dFont->GetPointSize();
11124 pointsize /= OCPN_GetWinDIPScaleFactor();
11125
11126 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11127 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11128 false, dFont->GetFaceName());
11129
11130 dc.SetFont(*psRLI_font);
11131
11132 int w1, h1;
11133 int w2 = 0;
11134 int h2 = 0;
11135 int h, w;
11136
11137 int xp, yp;
11138 int hilite_offset = 3;
11139#ifdef __WXMAC__
11140 wxScreenDC sdc;
11141 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
11142 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
11143#else
11144 dc.GetTextExtent(first, &w1, &h1);
11145 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
11146#endif
11147
11148 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11149 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11150
11151 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
11152 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11153
11154 h = h1 + h2;
11155
11156 xp = ref_point.x - w;
11157 yp = ref_point.y;
11158 yp += hilite_offset;
11159
11160 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor(_T ( "YELO1" )), 172);
11161
11162 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" ))));
11163 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
11164
11165 dc.DrawText(first, xp, yp);
11166 if (second.Len()) dc.DrawText(second, xp, yp + h1);
11167}
11168
11169void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11170 if (!g_bAllowShipToActive) return;
11171
11172 Route *rt = g_pRouteMan->GetpActiveRoute();
11173 if (!rt) return;
11174
11175 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11176 wxPoint2DDouble pa, pb;
11177 GetDoubleCanvasPointPix(gLat, gLon, &pa);
11178 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11179
11180 // set pen
11181 int width =
11182 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11183 if (rt->m_width != wxPENSTYLE_INVALID)
11184 width = rt->m_width; // set route pen style if any
11185 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11186 g_shipToActiveStyle, 5)]; // get setting pen style
11187 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11188 wxColour color =
11189 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11190 : // set setting route pen color
11191 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11192 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11193
11194 dc.SetPen(*mypen);
11195 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11196
11197 if (!Use_Opengl)
11198 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11199 (int)pb.m_y, GetVP(), true);
11200
11201#ifdef ocpnUSE_GL
11202 else {
11203#ifdef USE_ANDROID_GLES2
11204 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11205#else
11206 if (style != wxPENSTYLE_SOLID) {
11207 if (glChartCanvas::dash_map.find(style) !=
11208 glChartCanvas::dash_map.end()) {
11209 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11210 dc.SetPen(*mypen);
11211 }
11212 }
11213 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11214#endif
11215
11216 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11217 (int)pb.m_x, (int)pb.m_y, GetVP());
11218 }
11219#endif
11220 }
11221}
11222
11223void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11224 Route *route = 0;
11225 if (m_routeState >= 2) route = m_pMouseRoute;
11226 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11227 route = m_pMeasureRoute;
11228
11229 if (!route) return;
11230
11231 // Validate route pointer
11232 if (!g_pRouteMan->IsRouteValid(route)) return;
11233
11234 double render_lat = m_cursor_lat;
11235 double render_lon = m_cursor_lon;
11236
11237 int np = route->GetnPoints();
11238 if (np) {
11239 if (g_btouch && (np > 1)) np--;
11240 RoutePoint rp = route->GetPoint(np);
11241 render_lat = rp.m_lat;
11242 render_lon = rp.m_lon;
11243 }
11244
11245 double rhumbBearing, rhumbDist;
11246 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11247 &rhumbBearing, &rhumbDist);
11248 double brg = rhumbBearing;
11249 double dist = rhumbDist;
11250
11251 // Skip GreatCircle rubberbanding on touch devices.
11252 if (!g_btouch) {
11253 double gcBearing, gcBearing2, gcDist;
11254 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11255 m_cursor_lat, &gcDist, &gcBearing,
11256 &gcBearing2);
11257 double gcDistm = gcDist / 1852.0;
11258
11259 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11260 rhumbBearing = 90.;
11261
11262 wxPoint destPoint, lastPoint;
11263
11264 route->m_NextLegGreatCircle = false;
11265 int milesDiff = rhumbDist - gcDistm;
11266 if (milesDiff > 1) {
11267 brg = gcBearing;
11268 dist = gcDistm;
11269 route->m_NextLegGreatCircle = true;
11270 }
11271
11272 // FIXME (MacOS, the first segment is rendered wrong)
11273 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11274 &lastPoint);
11275
11276 if (route->m_NextLegGreatCircle) {
11277 for (int i = 1; i <= milesDiff; i++) {
11278 double p = (double)i * (1.0 / (double)milesDiff);
11279 double pLat, pLon;
11280 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11281 &pLon, &pLat, &gcBearing2);
11282 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11283 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11284 false);
11285 lastPoint = destPoint;
11286 }
11287 } else {
11288 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11289 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11290 false);
11291 if (m_bMeasure_DistCircle) {
11292 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11293 powf((float)(r_rband.y - lastPoint.y), 2));
11294
11295 dc.SetPen(*g_pRouteMan->GetRoutePen());
11296 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11297 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11298 }
11299 }
11300 }
11301 }
11302
11303 wxString routeInfo;
11304 double varBrg = 0;
11305 if (g_bShowTrue)
11306 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11307 0x00B0);
11308
11309 if (g_bShowMag) {
11310 double latAverage = (m_cursor_lat + render_lat) / 2;
11311 double lonAverage = (m_cursor_lon + render_lon) / 2;
11312 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11313
11314 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11315 (int)varBrg, 0x00B0);
11316 }
11317 routeInfo << _T(" ") << FormatDistanceAdaptive(dist);
11318
11319 // To make it easier to use a route as a bearing on a charted object add for
11320 // the first leg also the reverse bearing.
11321 if (np == 1) {
11322 routeInfo << "\nReverse: ";
11323 if (g_bShowTrue)
11324 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11325 (int)(brg + 180.) % 360, 0x00B0);
11326 if (g_bShowMag)
11327 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11328 (int)(varBrg + 180.) % 360, 0x00B0);
11329 }
11330
11331 wxString s0;
11332 if (!route->m_bIsInLayer)
11333 s0.Append(_("Route") + _T(": "));
11334 else
11335 s0.Append(_("Layer Route: "));
11336
11337 double disp_length = route->m_route_length;
11338 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11339 s0 += FormatDistanceAdaptive(disp_length);
11340
11341 RouteLegInfo(dc, r_rband, routeInfo, s0);
11342
11343 m_brepaint_piano = true;
11344}
11345
11346void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11347 if (!m_bShowVisibleSectors) return;
11348
11349 if (g_bDeferredInitDone) {
11350 // need to re-evaluate sectors?
11351 double rhumbBearing, rhumbDist;
11352 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11353 &rhumbBearing, &rhumbDist);
11354
11355 if (rhumbDist > 0.05) // miles
11356 {
11357 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11358 m_sectorlegsVisible);
11359 m_sector_glat = gLat;
11360 m_sector_glon = gLon;
11361 }
11362 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11363 }
11364}
11365
11366void ChartCanvas::WarpPointerDeferred(int x, int y) {
11367 warp_x = x;
11368 warp_y = y;
11369 warp_flag = true;
11370}
11371
11372int s_msg;
11373
11374void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11375 if (!ps52plib) return;
11376
11377 if (VPoint.b_quilt) { // quilted
11378 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11379
11380 if (m_pQuilt->IsQuiltVector()) {
11381 if (ps52plib->GetStateHash() != m_s52StateHash) {
11382 UpdateS52State();
11383 m_s52StateHash = ps52plib->GetStateHash();
11384 }
11385 }
11386 } else {
11387 if (ps52plib->GetStateHash() != m_s52StateHash) {
11388 UpdateS52State();
11389 m_s52StateHash = ps52plib->GetStateHash();
11390 }
11391 }
11392
11393 // Plugin charts
11394 bool bSendPlibState = true;
11395 if (VPoint.b_quilt) { // quilted
11396 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11397 }
11398
11399 if (bSendPlibState) {
11400 wxJSONValue v;
11401 v[_T("OpenCPN Version Major")] = VERSION_MAJOR;
11402 v[_T("OpenCPN Version Minor")] = VERSION_MINOR;
11403 v[_T("OpenCPN Version Patch")] = VERSION_PATCH;
11404 v[_T("OpenCPN Version Date")] = VERSION_DATE;
11405 v[_T("OpenCPN Version Full")] = VERSION_FULL;
11406
11407 // S52PLIB state
11408 v[_T("OpenCPN S52PLIB ShowText")] = GetShowENCText();
11409 v[_T("OpenCPN S52PLIB ShowSoundings")] = GetShowENCDepth();
11410 v[_T("OpenCPN S52PLIB ShowLights")] = GetShowENCLights();
11411 v[_T("OpenCPN S52PLIB ShowAnchorConditions")] = m_encShowAnchor;
11412 v[_T("OpenCPN S52PLIB ShowQualityOfData")] = GetShowENCDataQual();
11413 v[_T("OpenCPN S52PLIB ShowATONLabel")] = GetShowENCBuoyLabels();
11414 v[_T("OpenCPN S52PLIB ShowLightDescription")] = GetShowENCLightDesc();
11415
11416 v[_T("OpenCPN S52PLIB DisplayCategory")] = GetENCDisplayCategory();
11417
11418 v[_T("OpenCPN S52PLIB SoundingsFactor")] = g_ENCSoundingScaleFactor;
11419 v[_T("OpenCPN S52PLIB TextFactor")] = g_ENCTextScaleFactor;
11420
11421 // Global S52 options
11422
11423 v[_T("OpenCPN S52PLIB MetaDisplay")] = ps52plib->m_bShowMeta;
11424 v[_T("OpenCPN S52PLIB DeclutterText")] = ps52plib->m_bDeClutterText;
11425 v[_T("OpenCPN S52PLIB ShowNationalText")] = ps52plib->m_bShowNationalTexts;
11426 v[_T("OpenCPN S52PLIB ShowImportantTextOnly")] =
11427 ps52plib->m_bShowS57ImportantTextOnly;
11428 v[_T("OpenCPN S52PLIB UseSCAMIN")] = ps52plib->m_bUseSCAMIN;
11429 v[_T("OpenCPN S52PLIB UseSUPER_SCAMIN")] = ps52plib->m_bUseSUPER_SCAMIN;
11430 v[_T("OpenCPN S52PLIB SymbolStyle")] = ps52plib->m_nSymbolStyle;
11431 v[_T("OpenCPN S52PLIB BoundaryStyle")] = ps52plib->m_nBoundaryStyle;
11432 v[_T("OpenCPN S52PLIB ColorShades")] =
11433 S52_getMarinerParam(S52_MAR_TWO_SHADES);
11434
11435 // Some global GUI parameters, for completeness
11436 v[_T("OpenCPN Zoom Mod Vector")] = g_chart_zoom_modifier_vector;
11437 v[_T("OpenCPN Zoom Mod Other")] = g_chart_zoom_modifier_raster;
11438 v[_T("OpenCPN Scale Factor Exp")] =
11439 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11440 v[_T("OpenCPN Display Width")] = (int)g_display_size_mm;
11441
11442 wxJSONWriter w;
11443 wxString out;
11444 w.Write(v, out);
11445
11446 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11447 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11448 g_lastS52PLIBPluginMessage = out;
11449 }
11450 }
11451}
11452int spaint;
11453int s_in_update;
11454void ChartCanvas::OnPaint(wxPaintEvent &event) {
11455 wxPaintDC dc(this);
11456
11457 // GetToolbar()->Show( m_bToolbarEnable );
11458
11459 // Paint updates may have been externally disabled (temporarily, to avoid
11460 // Yield() recursion performance loss) It is important that the wxPaintDC is
11461 // built, even if we elect to not process this paint message. Otherwise, the
11462 // paint message may not be removed from the message queue, esp on Windows.
11463 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11464
11465 if (!m_b_paint_enable) {
11466 return;
11467 }
11468
11469 // If necessary, reconfigure the S52 PLIB
11470 UpdateCanvasS52PLIBConfig();
11471
11472#ifdef ocpnUSE_GL
11473 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11474
11475 if (m_glcc && g_bopengl) {
11476 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11477 s_in_update++;
11478 m_glcc->Update();
11479 s_in_update--;
11480 }
11481
11482 return;
11483 }
11484#endif
11485
11486 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11487
11488 wxRegion ru = GetUpdateRegion();
11489
11490 int rx, ry, rwidth, rheight;
11491 ru.GetBox(rx, ry, rwidth, rheight);
11492 // printf("%d Onpaint update region box: %d %d %d %d\n", spaint++, rx, ry,
11493 // rwidth, rheight);
11494
11495#ifdef ocpnUSE_DIBSECTION
11496 ocpnMemDC temp_dc;
11497#else
11498 wxMemoryDC temp_dc;
11499#endif
11500
11501 long height = GetVP().pix_height;
11502
11503#ifdef __WXMAC__
11504 // On OS X we have to explicitly extend the region for the piano area
11505 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11506 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11507 height += m_Piano->GetHeight();
11508#endif // __WXMAC__
11509 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11510
11511 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11512 if (pthumbwin) {
11513 int thumbx, thumby, thumbsx, thumbsy;
11514 pthumbwin->GetPosition(&thumbx, &thumby);
11515 pthumbwin->GetSize(&thumbsx, &thumbsy);
11516 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11517
11518 if (pthumbwin->IsShown()) {
11519 rgn_chart.Subtract(rgn_thumbwin);
11520 ru.Subtract(rgn_thumbwin);
11521 }
11522 }
11523
11524 // subtract the chart bar if it isn't transparent, and determine if we need to
11525 // paint it
11526 wxRegion rgn_blit = ru;
11527 if (g_bShowChartBar) {
11528 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11529 GetClientSize().x, m_Piano->GetHeight());
11530
11531 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11532 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11533 if (style->chartStatusWindowTransparent)
11534 m_brepaint_piano = true;
11535 else
11536 ru.Subtract(chart_bar_rect);
11537 }
11538 }
11539
11540 if (m_Compass && m_Compass->IsShown()) {
11541 wxRect compassRect = m_Compass->GetRect();
11542 if (ru.Contains(compassRect) != wxOutRegion) {
11543 ru.Subtract(compassRect);
11544 }
11545 }
11546
11547 wxRect noteRect = m_notification_button->GetRect();
11548 if (ru.Contains(noteRect) != wxOutRegion) {
11549 ru.Subtract(noteRect);
11550 }
11551
11552 // Is this viewpoint the same as the previously painted one?
11553 bool b_newview = true;
11554
11555 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11556 (m_cache_vp.rotation == VPoint.rotation) &&
11557 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11558 m_cache_vp.IsValid()) {
11559 b_newview = false;
11560 }
11561
11562 // If the ViewPort is skewed or rotated, we may be able to use the cached
11563 // rotated bitmap.
11564 bool b_rcache_ok = false;
11565 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11566 b_rcache_ok = !b_newview;
11567
11568 // Make a special VP
11569 if (VPoint.b_MercatorProjectionOverride)
11570 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11571 ViewPort svp = VPoint;
11572
11573 svp.pix_width = svp.rv_rect.width;
11574 svp.pix_height = svp.rv_rect.height;
11575
11576 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11577 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11578 // VPoint.rv_rect.height);
11579
11580 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11581
11582 // If we are going to use the cached rotated image, there is no need to fetch
11583 // any chart data and this will do it...
11584 if (b_rcache_ok) chart_get_region.Clear();
11585
11586 // Blit pan acceleration
11587 if (VPoint.b_quilt) // quilted
11588 {
11589 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11590
11591 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11592
11593 bool busy = false;
11594 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11595 m_cache_vp.rotation != VPoint.rotation)) {
11596 AbstractPlatform::ShowBusySpinner();
11597 busy = true;
11598 }
11599
11600 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11601 (m_working_bm.GetHeight() != svp.pix_height))
11602 m_working_bm.Create(svp.pix_width, svp.pix_height,
11603 -1); // make sure the target is big enoug
11604
11605 if (fabs(VPoint.rotation) < 0.01) {
11606 bool b_save = true;
11607
11608 if (g_SencThreadManager) {
11609 if (g_SencThreadManager->GetJobCount()) {
11610 b_save = false;
11611 m_cache_vp.Invalidate();
11612 }
11613 }
11614
11615 // If the saved wxBitmap from last OnPaint is useable
11616 // calculate the blit parameters
11617
11618 // We can only do screen blit painting if subsequent ViewPorts differ by
11619 // whole pixels So, in small scale bFollow mode, force the full screen
11620 // render. This seems a hack....There may be better logic here.....
11621
11622 // if(m_bFollow)
11623 // b_save = false;
11624
11625 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11626 if (b_newview) {
11627 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11628 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11629
11630 int dy = c_new.y - c_old.y;
11631 int dx = c_new.x - c_old.x;
11632
11633 // printf("In OnPaint Trying Blit dx: %d
11634 // dy:%d\n\n", dx, dy);
11635
11636 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11637 if (dx || dy) {
11638 // Blit the reuseable portion of the cached wxBitmap to a working
11639 // bitmap
11640 temp_dc.SelectObject(m_working_bm);
11641
11642 wxMemoryDC cache_dc;
11643 cache_dc.SelectObject(m_cached_chart_bm);
11644
11645 if (dy > 0) {
11646 if (dx > 0) {
11647 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11648 VPoint.pix_height - dy, &cache_dc, dx, dy);
11649 } else {
11650 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11651 VPoint.pix_height - dy, &cache_dc, 0, dy);
11652 }
11653
11654 } else {
11655 if (dx > 0) {
11656 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11657 VPoint.pix_height + dy, &cache_dc, dx, 0);
11658 } else {
11659 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11660 VPoint.pix_height + dy, &cache_dc, 0, 0);
11661 }
11662 }
11663
11664 OCPNRegion update_region;
11665 if (dy) {
11666 if (dy > 0)
11667 update_region.Union(
11668 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11669 else
11670 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11671 }
11672
11673 if (dx) {
11674 if (dx > 0)
11675 update_region.Union(
11676 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11677 else
11678 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11679 }
11680
11681 // Render the new region
11682 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11683 update_region);
11684 cache_dc.SelectObject(wxNullBitmap);
11685 } else {
11686 // No sensible (dx, dy) change in the view, so use the cached
11687 // member bitmap
11688 temp_dc.SelectObject(m_cached_chart_bm);
11689 b_save = false;
11690 }
11691 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11692
11693 } else // not blitable
11694 {
11695 temp_dc.SelectObject(m_working_bm);
11696 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11697 chart_get_region);
11698 }
11699 } else {
11700 // No change in the view, so use the cached member bitmap2
11701 temp_dc.SelectObject(m_cached_chart_bm);
11702 b_save = false;
11703 }
11704 } else // cached bitmap is not yet valid
11705 {
11706 temp_dc.SelectObject(m_working_bm);
11707 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11708 chart_get_region);
11709 }
11710
11711 // Save the fully rendered quilt image as a wxBitmap member of this class
11712 if (b_save) {
11713 // if((m_cached_chart_bm.GetWidth() !=
11714 // svp.pix_width) ||
11715 // (m_cached_chart_bm.GetHeight() !=
11716 // svp.pix_height))
11717 // m_cached_chart_bm.Create(svp.pix_width,
11718 // svp.pix_height, -1); // target wxBitmap
11719 // is big enough
11720 wxMemoryDC scratch_dc_0;
11721 scratch_dc_0.SelectObject(m_cached_chart_bm);
11722 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11723
11724 scratch_dc_0.SelectObject(wxNullBitmap);
11725
11726 m_bm_cache_vp =
11727 VPoint; // save the ViewPort associated with the cached wxBitmap
11728 }
11729 }
11730
11731 else // quilted, rotated
11732 {
11733 temp_dc.SelectObject(m_working_bm);
11734 OCPNRegion chart_get_all_region(
11735 wxRect(0, 0, svp.pix_width, svp.pix_height));
11736 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11737 chart_get_all_region);
11738 }
11739
11740 AbstractPlatform::HideBusySpinner();
11741
11742 }
11743
11744 else // not quilted
11745 {
11746 if (!m_singleChart) {
11747 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
11748 dc.Clear();
11749 return;
11750 }
11751
11752 if (!chart_get_region.IsEmpty()) {
11753 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
11754 }
11755 }
11756
11757 if (temp_dc.IsOk()) {
11758 // Arrange to render the World Chart vector data behind the rendered
11759 // current chart so that uncovered canvas areas show at least the world
11760 // chart.
11761 OCPNRegion chartValidRegion;
11762 if (!VPoint.b_quilt) {
11763 // Make a region covering the current chart on the canvas
11764
11765 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
11766 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
11767 else {
11768 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
11769 // require that the viewport passed here have pix_width and pix_height
11770 // set to the actual display, not the virtual (rv_rect) sizes
11771 // (the vector calculations require the virtual sizes in svp)
11772
11773 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
11774 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
11775 }
11776 } else
11777 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
11778
11779 temp_dc.DestroyClippingRegion();
11780
11781 // Copy current chart region
11782 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
11783
11784 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
11785
11786 if (!backgroundRegion.IsEmpty()) {
11787 // Draw the Background Chart only in the areas NOT covered by the
11788 // current chart view
11789
11790 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
11791 clipping regions with more than 1 rectangle so... */
11792 wxColour water = pWorldBackgroundChart->water;
11793 if (water.IsOk()) {
11794 temp_dc.SetPen(*wxTRANSPARENT_PEN);
11795 temp_dc.SetBrush(wxBrush(water));
11796 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
11797 while (upd.HaveRects()) {
11798 wxRect rect = upd.GetRect();
11799 temp_dc.DrawRectangle(rect);
11800 upd.NextRect();
11801 }
11802 }
11803 // Associate with temp_dc
11804 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
11805 temp_dc.SetDeviceClippingRegion(*clip_region);
11806 delete clip_region;
11807
11808 ocpnDC bgdc(temp_dc);
11809 double r = VPoint.rotation;
11810 SetVPRotation(VPoint.skew);
11811
11812 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
11813 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
11814
11815 SetVPRotation(r);
11816 }
11817 } // temp_dc.IsOk();
11818
11819 wxMemoryDC *pChartDC = &temp_dc;
11820 wxMemoryDC rotd_dc;
11821
11822 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
11823 // Can we use the current rotated image cache?
11824 if (!b_rcache_ok) {
11825#ifdef __WXMSW__
11826 wxMemoryDC tbase_dc;
11827 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
11828 tbase_dc.SelectObject(bm_base);
11829 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11830 tbase_dc.SelectObject(wxNullBitmap);
11831#else
11832 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
11833#endif
11834
11835 wxImage base_image;
11836 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
11837
11838 // Use a local static image rotator to improve wxWidgets code profile
11839 // Especially, on GTK the wxRound and wxRealPoint functions are very
11840 // expensive.....
11841
11842 double angle = GetVP().skew - GetVP().rotation;
11843 wxImage ri;
11844 bool b_rot_ok = false;
11845 if (base_image.IsOk()) {
11846 ViewPort rot_vp = GetVP();
11847
11848 m_b_rot_hidef = false;
11849
11850 ri = Image_Rotate(
11851 base_image, angle,
11852 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
11853 m_b_rot_hidef, &m_roffset);
11854
11855 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11856 (rot_vp.rotation == VPoint.rotation) &&
11857 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
11858 rot_vp.IsValid() && (ri.IsOk())) {
11859 b_rot_ok = true;
11860 }
11861 }
11862
11863 if (b_rot_ok) {
11864 delete m_prot_bm;
11865 m_prot_bm = new wxBitmap(ri);
11866 }
11867
11868 m_roffset.x += VPoint.rv_rect.x;
11869 m_roffset.y += VPoint.rv_rect.y;
11870 }
11871
11872 if (m_prot_bm && m_prot_bm->IsOk()) {
11873 rotd_dc.SelectObject(*m_prot_bm);
11874 pChartDC = &rotd_dc;
11875 } else {
11876 pChartDC = &temp_dc;
11877 m_roffset = wxPoint(0, 0);
11878 }
11879 } else { // unrotated
11880 pChartDC = &temp_dc;
11881 m_roffset = wxPoint(0, 0);
11882 }
11883
11884 wxPoint offset = m_roffset;
11885
11886 // Save the PixelCache viewpoint for next time
11887 m_cache_vp = VPoint;
11888
11889 // Set up a scratch DC for overlay objects
11890 wxMemoryDC mscratch_dc;
11891 mscratch_dc.SelectObject(*pscratch_bm);
11892
11893 mscratch_dc.ResetBoundingBox();
11894 mscratch_dc.DestroyClippingRegion();
11895 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
11896
11897 // Blit the externally invalidated areas of the chart onto the scratch dc
11898 wxRegionIterator upd(rgn_blit); // get the update rect list
11899 while (upd) {
11900 wxRect rect = upd.GetRect();
11901
11902 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
11903 rect.x - offset.x, rect.y - offset.y);
11904 upd++;
11905 }
11906
11907 // If multi-canvas, indicate which canvas has keyboard focus
11908 // by drawing a simple blue bar at the top.
11909 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
11910 if (this == wxWindow::FindFocus()) {
11911 g_focusCanvas = this;
11912
11913 wxColour colour = GetGlobalColor(_T("BLUE4"));
11914 mscratch_dc.SetPen(wxPen(colour));
11915 mscratch_dc.SetBrush(wxBrush(colour));
11916
11917 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
11918 mscratch_dc.DrawRectangle(activeRect);
11919 }
11920 }
11921
11922 // Any MBtiles?
11923 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
11924 unsigned int im = stackIndexArray.size();
11925 if (VPoint.b_quilt && im > 0) {
11926 std::vector<int> tiles_to_show;
11927 for (unsigned int is = 0; is < im; is++) {
11928 const ChartTableEntry &cte =
11929 ChartData->GetChartTableEntry(stackIndexArray[is]);
11930 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
11931 continue;
11932 }
11933 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
11934 tiles_to_show.push_back(stackIndexArray[is]);
11935 }
11936 }
11937
11938 if (tiles_to_show.size())
11939 SetAlertString(_("MBTile requires OpenGL to be enabled"));
11940 }
11941
11942 // May get an unexpected OnPaint call while switching display modes
11943 // Guard for that.
11944 if (!g_bopengl) {
11945 ocpnDC scratch_dc(mscratch_dc);
11946 RenderAlertMessage(mscratch_dc, GetVP());
11947 }
11948
11949#if 0
11950 // quiting?
11951 if (g_bquiting) {
11952#ifdef ocpnUSE_DIBSECTION
11953 ocpnMemDC q_dc;
11954#else
11955 wxMemoryDC q_dc;
11956#endif
11957 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
11958 q_dc.SelectObject(qbm);
11959
11960 // Get a copy of the screen
11961 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
11962
11963 // Draw a rectangle over the screen with a stipple brush
11964 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
11965 q_dc.SetBrush(qbr);
11966 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
11967
11968 // Blit back into source
11969 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
11970 wxCOPY);
11971
11972 q_dc.SelectObject(wxNullBitmap);
11973 }
11974#endif
11975
11976#if 0
11977 // It is possible that this two-step method may be reuired for some platforms.
11978 // So, retain in the code base to aid recovery if necessary
11979
11980 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
11981 if( VPoint.b_quilt ) {
11982 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
11983 ChartBase *chart = m_pQuilt->GetRefChart();
11984 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
11985
11986 // Clear the text Global declutter list
11987 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
11988 if(ChPI)
11989 ChPI->ClearPLIBTextList();
11990 else{
11991 if(ps52plib)
11992 ps52plib->ClearTextList();
11993 }
11994
11995 wxMemoryDC t_dc;
11996 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
11997
11998 wxColor maskBackground = wxColour(1,0,0);
11999 t_dc.SelectObject( qbm );
12000 t_dc.SetBackground(wxBrush(maskBackground));
12001 t_dc.Clear();
12002
12003 // Copy the scratch DC into the new bitmap
12004 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12005
12006 // Render the text to the new bitmap
12007 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12008 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12009
12010 // Copy the new bitmap back to the scratch dc
12011 wxRegionIterator upd_final( ru );
12012 while( upd_final ) {
12013 wxRect rect = upd_final.GetRect();
12014 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12015 upd_final++;
12016 }
12017
12018 t_dc.SelectObject( wxNullBitmap );
12019 }
12020 }
12021 }
12022#endif
12023 // Direct rendering model...
12024 if (VPoint.b_quilt) {
12025 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12026 ChartBase *chart = m_pQuilt->GetRefChart();
12027 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12028 // Clear the text Global declutter list
12029 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12030 if (ChPI)
12031 ChPI->ClearPLIBTextList();
12032 else {
12033 if (ps52plib) ps52plib->ClearTextList();
12034 }
12035
12036 // Render the text directly to the scratch bitmap
12037 OCPNRegion chart_all_text_region(
12038 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12039
12040 if (g_bShowChartBar && m_Piano) {
12041 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12042 GetVP().pix_width, m_Piano->GetHeight());
12043
12044 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12045 if (!style->chartStatusWindowTransparent)
12046 chart_all_text_region.Subtract(chart_bar_rect);
12047 }
12048
12049 if (m_Compass && m_Compass->IsShown()) {
12050 wxRect compassRect = m_Compass->GetRect();
12051 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12052 chart_all_text_region.Subtract(compassRect);
12053 }
12054 }
12055
12056 mscratch_dc.DestroyClippingRegion();
12057
12058 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12059 chart_all_text_region);
12060 }
12061 }
12062 }
12063
12064 // Now that charts are fully rendered, apply the overlay objects as decals.
12065 ocpnDC scratch_dc(mscratch_dc);
12066 DrawOverlayObjects(scratch_dc, ru);
12067
12068 // And finally, blit the scratch dc onto the physical dc
12069 wxRegionIterator upd_final(rgn_blit);
12070 while (upd_final) {
12071 wxRect rect = upd_final.GetRect();
12072 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12073 rect.y);
12074 upd_final++;
12075 }
12076
12077 // Deselect the chart bitmap from the temp_dc, so that it will not be
12078 // destroyed in the temp_dc dtor
12079 temp_dc.SelectObject(wxNullBitmap);
12080 // And for the scratch bitmap
12081 mscratch_dc.SelectObject(wxNullBitmap);
12082
12083 dc.DestroyClippingRegion();
12084
12085 PaintCleanup();
12086}
12087
12088void ChartCanvas::PaintCleanup() {
12089 // Handle the current graphic window, if present
12090
12091 if (pCwin) {
12092 pCwin->Show();
12093 if (m_bTCupdate) {
12094 pCwin->Refresh();
12095 pCwin->Update();
12096 }
12097 }
12098
12099 // And set flags for next time
12100 m_bTCupdate = false;
12101
12102 // Handle deferred WarpPointer
12103 if (warp_flag) {
12104 WarpPointer(warp_x, warp_y);
12105 warp_flag = false;
12106 }
12107
12108 // Start movement timers, this runs nearly immediately.
12109 // the reason we cannot simply call it directly is the
12110 // refresh events it emits may be blocked from this paint event
12111 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12112 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12113}
12114
12115#if 0
12116wxColour GetErrorGraphicColor(double val)
12117{
12118 /*
12119 double valm = wxMin(val_max, val);
12120
12121 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12122 unsigned char red = (unsigned char)(255 * (valm/val_max));
12123
12124 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12125
12126 hv.saturation = 1.0;
12127 hv.value = 1.0;
12128
12129 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12130 return wxColour(rv.red, rv.green, rv.blue);
12131 */
12132
12133 // HTML colors taken from NOAA WW3 Web representation
12134 wxColour c;
12135 if((val > 0) && (val < 1)) c.Set(_T("#002ad9"));
12136 else if((val >= 1) && (val < 2)) c.Set(_T("#006ed9"));
12137 else if((val >= 2) && (val < 3)) c.Set(_T("#00b2d9"));
12138 else if((val >= 3) && (val < 4)) c.Set(_T("#00d4d4"));
12139 else if((val >= 4) && (val < 5)) c.Set(_T("#00d9a6"));
12140 else if((val >= 5) && (val < 7)) c.Set(_T("#00d900"));
12141 else if((val >= 7) && (val < 9)) c.Set(_T("#95d900"));
12142 else if((val >= 9) && (val < 12)) c.Set(_T("#d9d900"));
12143 else if((val >= 12) && (val < 15)) c.Set(_T("#d9ae00"));
12144 else if((val >= 15) && (val < 18)) c.Set(_T("#d98300"));
12145 else if((val >= 18) && (val < 21)) c.Set(_T("#d95700"));
12146 else if((val >= 21) && (val < 24)) c.Set(_T("#d90000"));
12147 else if((val >= 24) && (val < 27)) c.Set(_T("#ae0000"));
12148 else if((val >= 27) && (val < 30)) c.Set(_T("#8c0000"));
12149 else if((val >= 30) && (val < 36)) c.Set(_T("#870000"));
12150 else if((val >= 36) && (val < 42)) c.Set(_T("#690000"));
12151 else if((val >= 42) && (val < 48)) c.Set(_T("#550000"));
12152 else if( val >= 48) c.Set(_T("#410000"));
12153
12154 return c;
12155}
12156
12157void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12158{
12159 wxImage gr_image(vp->pix_width, vp->pix_height);
12160 gr_image.InitAlpha();
12161
12162 double maxval = -10000;
12163 double minval = 10000;
12164
12165 double rlat, rlon;
12166 double glat, glon;
12167
12168 GetCanvasPixPoint(0, 0, rlat, rlon);
12169
12170 for(int i=1; i < vp->pix_height-1; i++)
12171 {
12172 for(int j=0; j < vp->pix_width; j++)
12173 {
12174 // Reference mercator value
12175// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12176
12177 // Georef value
12178 GetCanvasPixPoint(j, i, glat, glon);
12179
12180 maxval = wxMax(maxval, (glat - rlat));
12181 minval = wxMin(minval, (glat - rlat));
12182
12183 }
12184 rlat = glat;
12185 }
12186
12187 GetCanvasPixPoint(0, 0, rlat, rlon);
12188 for(int i=1; i < vp->pix_height-1; i++)
12189 {
12190 for(int j=0; j < vp->pix_width; j++)
12191 {
12192 // Reference mercator value
12193// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12194
12195 // Georef value
12196 GetCanvasPixPoint(j, i, glat, glon);
12197
12198 double f = ((glat - rlat)-minval)/(maxval - minval);
12199
12200 double dy = (f * 40);
12201
12202 wxColour c = GetErrorGraphicColor(dy);
12203 unsigned char r = c.Red();
12204 unsigned char g = c.Green();
12205 unsigned char b = c.Blue();
12206
12207 gr_image.SetRGB(j, i, r,g,b);
12208 if((glat - rlat )!= 0)
12209 gr_image.SetAlpha(j, i, 128);
12210 else
12211 gr_image.SetAlpha(j, i, 255);
12212
12213 }
12214 rlat = glat;
12215 }
12216
12217 // Create a Bitmap
12218 wxBitmap *pbm = new wxBitmap(gr_image);
12219 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12220 pbm->SetMask(gr_mask);
12221
12222 pmdc->DrawBitmap(*pbm, 0,0);
12223
12224 delete pbm;
12225
12226}
12227
12228#endif
12229
12230void ChartCanvas::CancelMouseRoute() {
12231 m_routeState = 0;
12232 m_pMouseRoute = NULL;
12233 m_bDrawingRoute = false;
12234}
12235
12236int ChartCanvas::GetNextContextMenuId() {
12237 return CanvasMenuHandler::GetNextContextMenuId();
12238}
12239
12240bool ChartCanvas::SetCursor(const wxCursor &c) {
12241#ifdef ocpnUSE_GL
12242 if (g_bopengl && m_glcc)
12243 return m_glcc->SetCursor(c);
12244 else
12245#endif
12246 return wxWindow::SetCursor(c);
12247}
12248
12249void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12250 if (g_bquiting) return;
12251 // Keep the mouse position members up to date
12252 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12253
12254 // Retrigger the route leg popup timer
12255 // This handles the case when the chart is moving in auto-follow mode,
12256 // but no user mouse input is made. The timer handler may Hide() the
12257 // popup if the chart moved enough n.b. We use slightly longer oneshot
12258 // value to allow this method's Refresh() to complete before potentially
12259 // getting another Refresh() in the popup timer handler.
12260 if (!m_RolloverPopupTimer.IsRunning() &&
12261 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12262 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12263 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12264 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12265
12266#ifdef ocpnUSE_GL
12267 if (m_glcc && g_bopengl) {
12268 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12269 // overlay objects.
12270 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12271
12272 m_glcc->Refresh(eraseBackground,
12273 NULL); // We always are going to render the entire screen
12274 // anyway, so make
12275 // sure that the window managers understand the invalid area
12276 // is actually the entire client area.
12277
12278 // We need to selectively Refresh some child windows, if they are visible.
12279 // Note that some children are refreshed elsewhere on timer ticks, so don't
12280 // need attention here.
12281
12282 // Thumbnail chart
12283 if (pthumbwin && pthumbwin->IsShown()) {
12284 pthumbwin->Raise();
12285 pthumbwin->Refresh(false);
12286 }
12287
12288 // ChartInfo window
12289 if (m_pCIWin && m_pCIWin->IsShown()) {
12290 m_pCIWin->Raise();
12291 m_pCIWin->Refresh(false);
12292 }
12293
12294 // if(g_MainToolbar)
12295 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12296
12297 } else
12298#endif
12299 wxWindow::Refresh(eraseBackground, rect);
12300}
12301
12302void ChartCanvas::Update() {
12303 if (m_glcc && g_bopengl) {
12304#ifdef ocpnUSE_GL
12305 m_glcc->Update();
12306#endif
12307 } else
12308 wxWindow::Update();
12309}
12310
12311void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12312 if (!pemboss) return;
12313 int x = pemboss->x, y = pemboss->y;
12314 const double factor = 200;
12315
12316 wxASSERT_MSG(dc.GetDC(), wxT("DrawEmboss has no dc (opengl?)"));
12317 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12318 wxASSERT_MSG(pmdc, wxT("dc to EmbossCanvas not a memory dc"));
12319
12320 // Grab a snipped image out of the chart
12321 wxMemoryDC snip_dc;
12322 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12323 snip_dc.SelectObject(snip_bmp);
12324
12325 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12326 snip_dc.SelectObject(wxNullBitmap);
12327
12328 wxImage snip_img = snip_bmp.ConvertToImage();
12329
12330 // Apply Emboss map to the snip image
12331 unsigned char *pdata = snip_img.GetData();
12332 if (pdata) {
12333 for (int y = 0; y < pemboss->height; y++) {
12334 int map_index = (y * pemboss->width);
12335 for (int x = 0; x < pemboss->width; x++) {
12336 double val = (pemboss->pmap[map_index] * factor) / 256.;
12337
12338 int nred = (int)((*pdata) + val);
12339 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12340 *pdata++ = (unsigned char)nred;
12341
12342 int ngreen = (int)((*pdata) + val);
12343 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12344 *pdata++ = (unsigned char)ngreen;
12345
12346 int nblue = (int)((*pdata) + val);
12347 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12348 *pdata++ = (unsigned char)nblue;
12349
12350 map_index++;
12351 }
12352 }
12353 }
12354
12355 // Convert embossed snip to a bitmap
12356 wxBitmap emb_bmp(snip_img);
12357
12358 // Map to another memoryDC
12359 wxMemoryDC result_dc;
12360 result_dc.SelectObject(emb_bmp);
12361
12362 // Blit to target
12363 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12364
12365 result_dc.SelectObject(wxNullBitmap);
12366}
12367
12368emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12369 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12370
12371 if (GetQuiltMode()) {
12372 // disable Overzoom indicator for MBTiles
12373 int refIndex = GetQuiltRefChartdbIndex();
12374 if (refIndex >= 0) {
12375 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12376 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12377 if (current_type == CHART_TYPE_MBTILES) {
12378 ChartBase *pChart = m_pQuilt->GetRefChart();
12379 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12380 if (ptc) {
12381 zoom_factor = ptc->GetZoomFactor();
12382 }
12383 }
12384 }
12385
12386 if (zoom_factor <= 3.9) return NULL;
12387 } else {
12388 if (m_singleChart) {
12389 if (zoom_factor <= 3.9) return NULL;
12390 } else
12391 return NULL;
12392 }
12393
12394 if (m_pEM_OverZoom) {
12395 m_pEM_OverZoom->x = 4;
12396 m_pEM_OverZoom->y = 0;
12397 if (g_MainToolbar && IsPrimaryCanvas()) {
12398 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12399 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12400 }
12401 }
12402 return m_pEM_OverZoom;
12403}
12404
12405void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12406 GridDraw(dc);
12407
12408 // bool pluginOverlayRender = true;
12409 //
12410 // if(g_canvasConfig > 0){ // Multi canvas
12411 // if(IsPrimaryCanvas())
12412 // pluginOverlayRender = false;
12413 // }
12414
12415 g_overlayCanvas = this;
12416
12417 if (g_pi_manager) {
12418 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12419 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12420 OVERLAY_LEGACY);
12421 }
12422
12423 AISDrawAreaNotices(dc, GetVP(), this);
12424
12425 wxDC *pdc = dc.GetDC();
12426 if (pdc) {
12427 pdc->DestroyClippingRegion();
12428 wxDCClipper(*pdc, ru);
12429 }
12430
12431 if (m_bShowNavobjects) {
12432 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12433 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12434 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12435 DrawAnchorWatchPoints(dc);
12436 } else {
12437 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12438 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12439 }
12440
12441 AISDraw(dc, GetVP(), this);
12442 ShipDraw(dc);
12443 AlertDraw(dc);
12444
12445 RenderVisibleSectorLights(dc);
12446
12447 RenderAllChartOutlines(dc, GetVP());
12448 RenderRouteLegs(dc);
12449 RenderShipToActive(dc, false);
12450 ScaleBarDraw(dc);
12451 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12452 if (g_pi_manager) {
12453 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12454 OVERLAY_OVER_SHIPS);
12455 }
12456
12457 DrawEmboss(dc, EmbossDepthScale());
12458 DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12459 if (g_pi_manager) {
12460 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12461 OVERLAY_OVER_EMBOSS);
12462 }
12463
12464 if (m_bShowTide) {
12465 RebuildTideSelectList(GetVP().GetBBox());
12466 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12467 }
12468
12469 if (m_bShowCurrent) {
12470 RebuildCurrentSelectList(GetVP().GetBBox());
12471 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12472 }
12473
12474 if (!g_PrintingInProgress) {
12475 if (IsPrimaryCanvas()) {
12476 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12477 }
12478
12479 if (IsPrimaryCanvas()) {
12480 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12481 }
12482
12483 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12484
12485 if (m_pTrackRolloverWin) {
12486 m_pTrackRolloverWin->Draw(dc);
12487 m_brepaint_piano = true;
12488 }
12489
12490 if (m_pRouteRolloverWin) {
12491 m_pRouteRolloverWin->Draw(dc);
12492 m_brepaint_piano = true;
12493 }
12494
12495 if (m_pAISRolloverWin) {
12496 m_pAISRolloverWin->Draw(dc);
12497 m_brepaint_piano = true;
12498 }
12499 if (m_brepaint_piano && g_bShowChartBar) {
12500 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12501 }
12502
12503 if (m_Compass) m_Compass->Paint(dc);
12504
12505 if (!g_CanvasHideNotificationIcon) {
12506 auto &noteman = NotificationManager::GetInstance();
12507 if (noteman.GetNotificationCount()) {
12508 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12509 if (m_notification_button->UpdateStatus()) Refresh();
12510 m_notification_button->Show(true);
12511 m_notification_button->Paint(dc);
12512 } else {
12513 m_notification_button->Show(false);
12514 }
12515 }
12516 }
12517 if (g_pi_manager) {
12518 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12519 OVERLAY_OVER_UI);
12520 }
12521}
12522
12523emboss_data *ChartCanvas::EmbossDepthScale() {
12524 if (!m_bShowDepthUnits) return NULL;
12525
12526 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12527
12528 if (GetQuiltMode()) {
12529 wxString s = m_pQuilt->GetQuiltDepthUnit();
12530 s.MakeUpper();
12531 if (s == _T("FEET"))
12532 depth_unit_type = DEPTH_UNIT_FEET;
12533 else if (s.StartsWith(_T("FATHOMS")))
12534 depth_unit_type = DEPTH_UNIT_FATHOMS;
12535 else if (s.StartsWith(_T("METERS")))
12536 depth_unit_type = DEPTH_UNIT_METERS;
12537 else if (s.StartsWith(_T("METRES")))
12538 depth_unit_type = DEPTH_UNIT_METERS;
12539 else if (s.StartsWith(_T("METRIC")))
12540 depth_unit_type = DEPTH_UNIT_METERS;
12541 else if (s.StartsWith(_T("METER")))
12542 depth_unit_type = DEPTH_UNIT_METERS;
12543
12544 } else {
12545 if (m_singleChart) {
12546 depth_unit_type = m_singleChart->GetDepthUnitType();
12547 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12548 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12549 }
12550 }
12551
12552 emboss_data *ped = NULL;
12553 switch (depth_unit_type) {
12554 case DEPTH_UNIT_FEET:
12555 ped = m_pEM_Feet;
12556 break;
12557 case DEPTH_UNIT_METERS:
12558 ped = m_pEM_Meters;
12559 break;
12560 case DEPTH_UNIT_FATHOMS:
12561 ped = m_pEM_Fathoms;
12562 break;
12563 default:
12564 return NULL;
12565 }
12566
12567 ped->x = (GetVP().pix_width - ped->width);
12568
12569 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12570 wxRect r = m_Compass->GetRect();
12571 ped->y = r.y + r.height;
12572 } else {
12573 ped->y = 40;
12574 }
12575 return ped;
12576}
12577
12578void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12579 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12580 wxFont font;
12581 if (style->embossFont == wxEmptyString) {
12582 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12583 font = *dFont;
12584 font.SetPointSize(60);
12585 font.SetWeight(wxFONTWEIGHT_BOLD);
12586 } else
12587 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12588 wxFONTWEIGHT_BOLD, false, style->embossFont);
12589
12590 int emboss_width = 500;
12591 int emboss_height = 200;
12592
12593 // Free any existing emboss maps
12594 delete m_pEM_Feet;
12595 delete m_pEM_Meters;
12596 delete m_pEM_Fathoms;
12597
12598 // Create the 3 DepthUnit emboss map structures
12599 m_pEM_Feet =
12600 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12601 m_pEM_Meters =
12602 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12603 m_pEM_Fathoms =
12604 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12605}
12606
12607#define OVERZOOM_TEXT _("OverZoom")
12608
12609void ChartCanvas::SetOverzoomFont() {
12610 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12611 int w, h;
12612
12613 wxFont font;
12614 if (style->embossFont == wxEmptyString) {
12615 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12616 font = *dFont;
12617 font.SetPointSize(40);
12618 font.SetWeight(wxFONTWEIGHT_BOLD);
12619 } else
12620 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12621 wxFONTWEIGHT_BOLD, false, style->embossFont);
12622
12623 wxClientDC dc(this);
12624 dc.SetFont(font);
12625 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12626
12627 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12628 font.SetPointSize(font.GetPointSize() - 1);
12629 dc.SetFont(font);
12630 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12631 }
12632 m_overzoomFont = font;
12633 m_overzoomTextWidth = w;
12634 m_overzoomTextHeight = h;
12635}
12636
12637void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12638 delete m_pEM_OverZoom;
12639
12640 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12641 m_pEM_OverZoom =
12642 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12643 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12644}
12645
12646emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12647 int height, const wxString &str,
12648 ColorScheme cs) {
12649 int *pmap;
12650
12651 // Create a temporary bitmap
12652 wxBitmap bmp(width, height, -1);
12653
12654 // Create a memory DC
12655 wxMemoryDC temp_dc;
12656 temp_dc.SelectObject(bmp);
12657
12658 // Paint on it
12659 temp_dc.SetBackground(*wxWHITE_BRUSH);
12660 temp_dc.SetTextBackground(*wxWHITE);
12661 temp_dc.SetTextForeground(*wxBLACK);
12662
12663 temp_dc.Clear();
12664
12665 temp_dc.SetFont(font);
12666
12667 int str_w, str_h;
12668 temp_dc.GetTextExtent(str, &str_w, &str_h);
12669 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12670 temp_dc.DrawText(str, 1, 1);
12671
12672 // Deselect the bitmap
12673 temp_dc.SelectObject(wxNullBitmap);
12674
12675 // Convert bitmap the wxImage for manipulation
12676 wxImage img = bmp.ConvertToImage();
12677
12678 int image_width = str_w * 105 / 100;
12679 int image_height = str_h * 105 / 100;
12680 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12681 wxMin(image_height, img.GetHeight()));
12682 wxImage imgs = img.GetSubImage(r);
12683
12684 double val_factor;
12685 switch (cs) {
12686 case GLOBAL_COLOR_SCHEME_DAY:
12687 default:
12688 val_factor = 1;
12689 break;
12690 case GLOBAL_COLOR_SCHEME_DUSK:
12691 val_factor = .5;
12692 break;
12693 case GLOBAL_COLOR_SCHEME_NIGHT:
12694 val_factor = .25;
12695 break;
12696 }
12697
12698 int val;
12699 int index;
12700 const int w = imgs.GetWidth();
12701 const int h = imgs.GetHeight();
12702 pmap = (int *)calloc(w * h * sizeof(int), 1);
12703 // Create emboss map by differentiating the emboss image
12704 // and storing integer results in pmap
12705 // n.b. since the image is B/W, it is sufficient to check
12706 // one channel (i.e. red) only
12707 for (int y = 1; y < h - 1; y++) {
12708 for (int x = 1; x < w - 1; x++) {
12709 val =
12710 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
12711 val = (int)(val * val_factor);
12712 index = (y * w) + x;
12713 pmap[index] = val;
12714 }
12715 }
12716
12717 emboss_data *pret = new emboss_data;
12718 pret->pmap = pmap;
12719 pret->width = w;
12720 pret->height = h;
12721
12722 return pret;
12723}
12724
12725void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12726 Track *active_track = NULL;
12727 for (Track *pTrackDraw : g_TrackList) {
12728 if (g_pActiveTrack == pTrackDraw) {
12729 active_track = pTrackDraw;
12730 continue;
12731 }
12732
12733 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
12734 }
12735
12736 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12737}
12738
12739void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12740 Track *active_track = NULL;
12741 for (Track *pTrackDraw : g_TrackList) {
12742 if (g_pActiveTrack == pTrackDraw) {
12743 active_track = pTrackDraw;
12744 break;
12745 }
12746 }
12747 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12748}
12749
12750void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12751 Route *active_route = NULL;
12752
12753 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12754 node = node->GetNext()) {
12755 Route *pRouteDraw = node->GetData();
12756 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12757 active_route = pRouteDraw;
12758 continue;
12759 }
12760
12761 // if(m_canvasIndex == 1)
12762 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
12763 }
12764
12765 // Draw any active or selected route (or track) last, so that is is always on
12766 // top
12767 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12768}
12769
12770void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12771 Route *active_route = NULL;
12772
12773 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12774 node = node->GetNext()) {
12775 Route *pRouteDraw = node->GetData();
12776 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12777 active_route = pRouteDraw;
12778 break;
12779 }
12780 }
12781 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12782}
12783
12784void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12785 if (!pWayPointMan) return;
12786
12787 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12788
12789 while (node) {
12790 RoutePoint *pWP = node->GetData();
12791 if (pWP) {
12792 if (pWP->m_bIsInRoute) {
12793 node = node->GetNext();
12794 continue;
12795 }
12796
12797 /* technically incorrect... waypoint has bounding box */
12798 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
12799 RoutePointGui(*pWP).Draw(dc, this, NULL);
12800 else {
12801 // Are Range Rings enabled?
12802 if (pWP->GetShowWaypointRangeRings() &&
12803 (pWP->GetWaypointRangeRingsNumber() > 0)) {
12804 double factor = 1.00;
12805 if (pWP->GetWaypointRangeRingsStepUnits() ==
12806 1) // convert kilometers to NMi
12807 factor = 1 / 1.852;
12808
12809 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
12810 pWP->GetWaypointRangeRingsStep() / 60.;
12811 radius *= 2; // Fudge factor
12812
12813 LLBBox radar_box;
12814 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
12815 pWP->m_lat + radius, pWP->m_lon + radius);
12816 if (!BltBBox.IntersectOut(radar_box)) {
12817 RoutePointGui(*pWP).Draw(dc, this, NULL);
12818 }
12819 }
12820 }
12821 }
12822
12823 node = node->GetNext();
12824 }
12825}
12826
12827void ChartCanvas::DrawBlinkObjects(void) {
12828 // All RoutePoints
12829 wxRect update_rect;
12830
12831 if (!pWayPointMan) return;
12832
12833 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12834
12835 while (node) {
12836 RoutePoint *pWP = node->GetData();
12837 if (pWP) {
12838 if (pWP->m_bBlink) {
12839 update_rect.Union(pWP->CurrentRect_in_DC);
12840 }
12841 }
12842
12843 node = node->GetNext();
12844 }
12845 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
12846}
12847
12848void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
12849 // draw anchor watch rings, if activated
12850
12851 if (pAnchorWatchPoint1 || pAnchorWatchPoint2) {
12852 wxPoint r1, r2;
12853 wxPoint lAnchorPoint1, lAnchorPoint2;
12854 double lpp1 = 0.0;
12855 double lpp2 = 0.0;
12856 if (pAnchorWatchPoint1) {
12857 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
12858 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
12859 &lAnchorPoint1);
12860 }
12861 if (pAnchorWatchPoint2) {
12862 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
12863 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
12864 &lAnchorPoint2);
12865 }
12866
12867 wxPen ppPeng(GetGlobalColor(_T ( "UGREN" )), 2);
12868 wxPen ppPenr(GetGlobalColor(_T ( "URED" )), 2);
12869
12870 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
12871 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
12872 dc.SetBrush(*ppBrush);
12873
12874 if (lpp1 > 0) {
12875 dc.SetPen(ppPeng);
12876 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12877 }
12878
12879 if (lpp2 > 0) {
12880 dc.SetPen(ppPeng);
12881 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12882 }
12883
12884 if (lpp1 < 0) {
12885 dc.SetPen(ppPenr);
12886 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12887 }
12888
12889 if (lpp2 < 0) {
12890 dc.SetPen(ppPenr);
12891 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12892 }
12893 }
12894}
12895
12896double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
12897 double lpp = 0.;
12898 wxPoint r1;
12899 wxPoint lAnchorPoint;
12900 double d1 = 0.0;
12901 double dabs;
12902 double tlat1, tlon1;
12903
12904 if (pAnchorWatchPoint) {
12905 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
12906 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
12907 dabs = fabs(d1 / 1852.);
12908 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
12909 &tlat1, &tlon1);
12910 GetCanvasPointPix(tlat1, tlon1, &r1);
12911 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
12912 &lAnchorPoint);
12913 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
12914 pow((double)(lAnchorPoint.y - r1.y), 2));
12915
12916 // This is an entry watch
12917 if (d1 < 0) lpp = -lpp;
12918 }
12919 return lpp;
12920}
12921
12922//------------------------------------------------------------------------------------------
12923// Tides Support
12924//------------------------------------------------------------------------------------------
12925void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
12926 if (!ptcmgr) return;
12927
12928 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
12929
12930 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12931 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12932 double lon = pIDX->IDX_lon;
12933 double lat = pIDX->IDX_lat;
12934
12935 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12936 if ((type == 't') || (type == 'T')) {
12937 if (BBox.Contains(lat, lon)) {
12938 // Manage the point selection list
12939 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
12940 }
12941 }
12942 }
12943}
12944
12945extern wxDateTime gTimeSource;
12946
12947void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
12948 if (!ptcmgr) return;
12949
12950 wxDateTime this_now = gTimeSource;
12951 bool cur_time = !gTimeSource.IsValid();
12952 if (cur_time) this_now = wxDateTime::Now();
12953 time_t t_this_now = this_now.GetTicks();
12954
12955 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
12956 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
12957 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
12958 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )), 1,
12959 wxPENSTYLE_SOLID);
12960 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
12961 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )), 1,
12962 wxPENSTYLE_SOLID);
12963
12964 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
12965 GetGlobalColor(_T ( "GREEN1" )), wxBRUSHSTYLE_SOLID);
12966 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
12967 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )),
12968 wxBRUSHSTYLE_SOLID);
12969 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
12970 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )),
12971 wxBRUSHSTYLE_SOLID);
12972
12973 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
12974 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
12975 int font_size = wxMax(10, dFont->GetPointSize());
12976 font_size /= g_Platform->GetDisplayDIPMult(this);
12977 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
12978 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
12979 false, dFont->GetFaceName());
12980
12981 dc.SetPen(*pblack_pen);
12982 dc.SetBrush(*pgreen_brush);
12983
12984 wxBitmap bm;
12985 switch (m_cs) {
12986 case GLOBAL_COLOR_SCHEME_DAY:
12987 bm = m_bmTideDay;
12988 break;
12989 case GLOBAL_COLOR_SCHEME_DUSK:
12990 bm = m_bmTideDusk;
12991 break;
12992 case GLOBAL_COLOR_SCHEME_NIGHT:
12993 bm = m_bmTideNight;
12994 break;
12995 default:
12996 bm = m_bmTideDay;
12997 break;
12998 }
12999
13000 int bmw = bm.GetWidth();
13001 int bmh = bm.GetHeight();
13002
13003 float scale_factor = 1.0;
13004
13005 // Set the onscreen size of the symbol
13006 // Compensate for various display resolutions
13007 float icon_pixelRefDim = 45;
13008
13009 // Tidal report graphic is scaled by the text size of the label in use
13010 wxScreenDC sdc;
13011 int height;
13012 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13013 height *= g_Platform->GetDisplayDIPMult(this);
13014 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13015
13016 scale_factor *= pix_factor;
13017
13018 float user_scale_factor = g_ChartScaleFactorExp;
13019 if (g_ChartScaleFactorExp > 1.0)
13020 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13021 1.2; // soften the scale factor a bit
13022
13023 scale_factor *= user_scale_factor;
13024 scale_factor *= GetContentScaleFactor();
13025
13026 {
13027 double marge = 0.05;
13028 std::vector<LLBBox> drawn_boxes;
13029 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13030 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13031
13032 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13033 if ((type == 't') || (type == 'T')) // only Tides
13034 {
13035 double lon = pIDX->IDX_lon;
13036 double lat = pIDX->IDX_lat;
13037
13038 if (BBox.ContainsMarge(lat, lon, marge)) {
13039 // Avoid drawing detailed graphic for duplicate tide stations
13040 if (GetVP().chart_scale < 500000) {
13041 bool bdrawn = false;
13042 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13043 if (drawn_boxes[i].Contains(lat, lon)) {
13044 bdrawn = true;
13045 break;
13046 }
13047 }
13048 if (bdrawn) continue; // the station loop
13049
13050 LLBBox this_box;
13051 this_box.Set(lat, lon, lat, lon);
13052 this_box.EnLarge(.005);
13053 drawn_boxes.push_back(this_box);
13054 }
13055
13056 wxPoint r;
13057 GetCanvasPointPix(lat, lon, &r);
13058 // draw standard icons
13059 if (GetVP().chart_scale > 500000) {
13060 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13061 }
13062 // draw "extended" icons
13063 else {
13064 dc.SetFont(*plabelFont);
13065 {
13066 {
13067 float val, nowlev;
13068 float ltleve = 0.;
13069 float htleve = 0.;
13070 time_t tctime;
13071 time_t lttime = 0;
13072 time_t httime = 0;
13073 bool wt;
13074 // define if flood or ebb in the last ten minutes and verify if
13075 // data are useable
13076 if (ptcmgr->GetTideFlowSens(
13077 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13078 pIDX->IDX_rec_num, nowlev, val, wt)) {
13079 // search forward the first HW or LW near "now" ( starting at
13080 // "now" - ten minutes )
13081 ptcmgr->GetHightOrLowTide(
13082 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13083 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13084 wt, pIDX->IDX_rec_num, val, tctime);
13085 if (wt) {
13086 httime = tctime;
13087 htleve = val;
13088 } else {
13089 lttime = tctime;
13090 ltleve = val;
13091 }
13092 wt = !wt;
13093
13094 // then search opposite tide near "now"
13095 if (tctime > t_this_now) // search backward
13096 ptcmgr->GetHightOrLowTide(
13097 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13098 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13099 pIDX->IDX_rec_num, val, tctime);
13100 else
13101 // or search forward
13102 ptcmgr->GetHightOrLowTide(
13103 t_this_now, FORWARD_TEN_MINUTES_STEP,
13104 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13105 val, tctime);
13106 if (wt) {
13107 httime = tctime;
13108 htleve = val;
13109 } else {
13110 lttime = tctime;
13111 ltleve = val;
13112 }
13113
13114 // draw the tide rectangle:
13115
13116 // tide icon rectangle has default pre-scaled width = 12 ,
13117 // height = 45
13118 int width = (int)(12 * scale_factor + 0.5);
13119 int height = (int)(45 * scale_factor + 0.5);
13120 int linew = wxMax(1, (int)(scale_factor));
13121 int xDraw = r.x - (width / 2);
13122 int yDraw = r.y - (height / 2);
13123
13124 // process tide state ( %height and flow sens )
13125 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13126 int hs = (httime > lttime) ? -4 : 4;
13127 hs *= (int)(scale_factor + 0.5);
13128 if (ts > 0.995 || ts < 0.005) hs = 0;
13129 int ht_y = (int)(height * ts);
13130
13131 // draw yellow tide rectangle outlined in black
13132 pblack_pen->SetWidth(linew);
13133 dc.SetPen(*pblack_pen);
13134 dc.SetBrush(*pyelo_brush);
13135 dc.DrawRectangle(xDraw, yDraw, width, height);
13136
13137 // draw blue rectangle as water height, smaller in width than
13138 // yellow rectangle
13139 dc.SetPen(*pblue_pen);
13140 dc.SetBrush(*pblue_brush);
13141 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13142 (width - (4 * linew)), height - ht_y);
13143
13144 // draw sens arrows (ensure they are not "under-drawn" by top
13145 // line of blue rectangle )
13146 int hl;
13147 wxPoint arrow[3];
13148 arrow[0].x = xDraw + 2 * linew;
13149 arrow[1].x = xDraw + width / 2;
13150 arrow[2].x = xDraw + width - 2 * linew;
13151 pyelo_pen->SetWidth(linew);
13152 pblue_pen->SetWidth(linew);
13153 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13154 {
13155 hl = (int)(height * 0.25) + yDraw;
13156 arrow[0].y = hl;
13157 arrow[1].y = hl + hs;
13158 arrow[2].y = hl;
13159 if (ts < 0.15)
13160 dc.SetPen(*pyelo_pen);
13161 else
13162 dc.SetPen(*pblue_pen);
13163 dc.DrawLines(3, arrow);
13164 }
13165 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13166 {
13167 hl = (int)(height * 0.5) + yDraw;
13168 arrow[0].y = hl;
13169 arrow[1].y = hl + hs;
13170 arrow[2].y = hl;
13171 if (ts < 0.40)
13172 dc.SetPen(*pyelo_pen);
13173 else
13174 dc.SetPen(*pblue_pen);
13175 dc.DrawLines(3, arrow);
13176 }
13177 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13178 {
13179 hl = (int)(height * 0.75) + yDraw;
13180 arrow[0].y = hl;
13181 arrow[1].y = hl + hs;
13182 arrow[2].y = hl;
13183 if (ts < 0.65)
13184 dc.SetPen(*pyelo_pen);
13185 else
13186 dc.SetPen(*pblue_pen);
13187 dc.DrawLines(3, arrow);
13188 }
13189 // draw tide level text
13190 wxString s;
13191 s.Printf(_T("%3.1f"), nowlev);
13192 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13193 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13194 int wx1;
13195 dc.GetTextExtent(s, &wx1, NULL);
13196 wx1 *= g_Platform->GetDisplayDIPMult(this);
13197 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13198 }
13199 }
13200 }
13201 }
13202 }
13203 }
13204 }
13205 }
13206}
13207
13208//------------------------------------------------------------------------------------------
13209// Currents Support
13210//------------------------------------------------------------------------------------------
13211
13212void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13213 if (!ptcmgr) return;
13214
13215 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13216
13217 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13218 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13219 double lon = pIDX->IDX_lon;
13220 double lat = pIDX->IDX_lat;
13221
13222 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13223 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13224 if ((BBox.Contains(lat, lon))) {
13225 // Manage the point selection list
13226 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13227 }
13228 }
13229 }
13230}
13231
13232void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13233 if (!ptcmgr) return;
13234
13235 float tcvalue, dir;
13236 bool bnew_val;
13237 char sbuf[20];
13238 wxFont *pTCFont;
13239 double lon_last = 0.;
13240 double lat_last = 0.;
13241 // arrow size for Raz Blanchard : 12 knots north
13242 double marge = 0.2;
13243 bool cur_time = !gTimeSource.IsValid();
13244
13245 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13246 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13247
13248 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
13249 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
13250 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13251 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )), 1,
13252 wxPENSTYLE_SOLID);
13253 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13254 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )),
13255 wxBRUSHSTYLE_SOLID);
13256 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13257 GetGlobalColor(_T ( "UIBDR" )), wxBRUSHSTYLE_SOLID);
13258 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13259 GetGlobalColor(_T ( "UINFD" )), wxBRUSHSTYLE_SOLID);
13260
13261 double skew_angle = GetVPRotation();
13262
13263 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13264 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13265 int font_size = wxMax(10, dFont->GetPointSize());
13266 font_size /= g_Platform->GetDisplayDIPMult(this);
13267 pTCFont = FontMgr::Get().FindOrCreateFont(
13268 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13269 false, dFont->GetFaceName());
13270
13271 float scale_factor = 1.0;
13272
13273 // Set the onscreen size of the symbol
13274 // Current report graphic is scaled by the text size of the label in use
13275 wxScreenDC sdc;
13276 int height;
13277 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13278 height *= g_Platform->GetDisplayDIPMult(this);
13279 float nominal_icon_size_pixels = 15;
13280 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13281
13282 scale_factor *= pix_factor;
13283
13284 float user_scale_factor = g_ChartScaleFactorExp;
13285 if (g_ChartScaleFactorExp > 1.0)
13286 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13287 1.2; // soften the scale factor a bit
13288
13289 scale_factor *= user_scale_factor;
13290
13291 scale_factor *= GetContentScaleFactor();
13292
13293 {
13294 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13295 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13296 double lon = pIDX->IDX_lon;
13297 double lat = pIDX->IDX_lat;
13298
13299 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13300 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13301 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13302 wxPoint r;
13303 GetCanvasPointPix(lat, lon, &r);
13304
13305 wxPoint d[4]; // points of a diamond at the current station location
13306 int dd = (int)(5.0 * scale_factor + 0.5);
13307 d[0].x = r.x;
13308 d[0].y = r.y + dd;
13309 d[1].x = r.x + dd;
13310 d[1].y = r.y;
13311 d[2].x = r.x;
13312 d[2].y = r.y - dd;
13313 d[3].x = r.x - dd;
13314 d[3].y = r.y;
13315
13316 if (1) {
13317 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13318 dc.SetPen(*pblack_pen);
13319 dc.SetBrush(*porange_brush);
13320 dc.DrawPolygon(4, d);
13321
13322 if (type == 'C') {
13323 dc.SetBrush(*pblack_brush);
13324 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13325 }
13326
13327 if (GetVP().chart_scale < 1000000) {
13328 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13329 continue;
13330 } else
13331 continue;
13332
13333 if (1 /*type == 'c'*/) {
13334 {
13335 // Get the display pixel location of the current station
13336 int pixxc, pixyc;
13337 pixxc = r.x;
13338 pixyc = r.y;
13339
13340 // Adjust drawing size using logarithmic scale. tcvalue is
13341 // current in knots
13342 double a1 = fabs(tcvalue) * 10.;
13343 // Current values <= 0.1 knot will have no arrow
13344 a1 = wxMax(1.0, a1);
13345 double a2 = log10(a1);
13346
13347 float cscale = scale_factor * a2 * 0.3;
13348
13349 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13350 dc.SetPen(*porange_pen);
13351 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13352 cscale);
13353 // Draw text, if enabled
13354
13355 if (bDrawCurrentValues) {
13356 dc.SetFont(*pTCFont);
13357 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13358 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13359 }
13360 }
13361 } // scale
13362 }
13363 /* This is useful for debugging the TC database
13364 else
13365 {
13366 dc.SetPen ( *porange_pen );
13367 dc.SetBrush ( *pgray_brush );
13368 dc.DrawPolygon ( 4, d );
13369 }
13370 */
13371 }
13372 lon_last = lon;
13373 lat_last = lat;
13374 }
13375 }
13376 }
13377}
13378
13379void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13380 pCwin = new TCWin(this, x, y, pvIDX);
13381}
13382
13383#define NUM_CURRENT_ARROW_POINTS 9
13384static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13385 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13386 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13387 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13388
13389void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13390 double scale) {
13391 if (scale > 1e-2) {
13392 float sin_rot = sin(rot_angle * PI / 180.);
13393 float cos_rot = cos(rot_angle * PI / 180.);
13394
13395 // Move to the first point
13396
13397 float xt = CurrentArrowArray[0].x;
13398 float yt = CurrentArrowArray[0].y;
13399
13400 float xp = (xt * cos_rot) - (yt * sin_rot);
13401 float yp = (xt * sin_rot) + (yt * cos_rot);
13402 int x1 = (int)(xp * scale);
13403 int y1 = (int)(yp * scale);
13404
13405 // Walk thru the point list
13406 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13407 xt = CurrentArrowArray[ip].x;
13408 yt = CurrentArrowArray[ip].y;
13409
13410 float xp = (xt * cos_rot) - (yt * sin_rot);
13411 float yp = (xt * sin_rot) + (yt * cos_rot);
13412 int x2 = (int)(xp * scale);
13413 int y2 = (int)(yp * scale);
13414
13415 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13416
13417 x1 = x2;
13418 y1 = y2;
13419 }
13420 }
13421}
13422
13423wxString ChartCanvas::FindValidUploadPort() {
13424 wxString port;
13425 // Try to use the saved persistent upload port first
13426 if (!g_uploadConnection.IsEmpty() &&
13427 g_uploadConnection.StartsWith(_T("Serial"))) {
13428 port = g_uploadConnection;
13429 }
13430
13431 else {
13432 // If there is no persistent upload port recorded (yet)
13433 // then use the first available serial connection which has output defined.
13434 for (auto *cp : TheConnectionParams()) {
13435 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13436 port << _T("Serial:") << cp->Port;
13437 }
13438 }
13439 return port;
13440}
13441
13442void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13443 if (!win) return;
13444
13445 if (NULL == g_pais_query_dialog_active) {
13446 int pos_x = g_ais_query_dialog_x;
13447 int pos_y = g_ais_query_dialog_y;
13448
13449 if (g_pais_query_dialog_active) {
13450 g_pais_query_dialog_active->Destroy();
13451 g_pais_query_dialog_active = new AISTargetQueryDialog();
13452 } else {
13453 g_pais_query_dialog_active = new AISTargetQueryDialog();
13454 }
13455
13456 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13457 wxPoint(pos_x, pos_y));
13458
13459 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13460 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13461 g_pais_query_dialog_active->SetMMSI(mmsi);
13462 g_pais_query_dialog_active->UpdateText();
13463 wxSize sz = g_pais_query_dialog_active->GetSize();
13464
13465 bool b_reset_pos = false;
13466#ifdef __WXMSW__
13467 // Support MultiMonitor setups which an allow negative window positions.
13468 // If the requested window title bar does not intersect any installed
13469 // monitor, then default to simple primary monitor positioning.
13470 RECT frame_title_rect;
13471 frame_title_rect.left = pos_x;
13472 frame_title_rect.top = pos_y;
13473 frame_title_rect.right = pos_x + sz.x;
13474 frame_title_rect.bottom = pos_y + 30;
13475
13476 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13477 b_reset_pos = true;
13478#else
13479
13480 // Make sure drag bar (title bar) of window intersects wxClient Area of
13481 // screen, with a little slop...
13482 wxRect window_title_rect; // conservative estimate
13483 window_title_rect.x = pos_x;
13484 window_title_rect.y = pos_y;
13485 window_title_rect.width = sz.x;
13486 window_title_rect.height = 30;
13487
13488 wxRect ClientRect = wxGetClientDisplayRect();
13489 ClientRect.Deflate(
13490 60, 60); // Prevent the new window from being too close to the edge
13491 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13492
13493#endif
13494
13495 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13496
13497 } else {
13498 g_pais_query_dialog_active->SetMMSI(mmsi);
13499 g_pais_query_dialog_active->UpdateText();
13500 }
13501
13502 g_pais_query_dialog_active->Show();
13503}
13504
13505void ChartCanvas::ToggleCanvasQuiltMode(void) {
13506 bool cur_mode = GetQuiltMode();
13507
13508 if (!GetQuiltMode())
13509 SetQuiltMode(true);
13510 else if (GetQuiltMode()) {
13511 SetQuiltMode(false);
13512 g_sticky_chart = GetQuiltReferenceChartIndex();
13513 }
13514
13515 if (cur_mode != GetQuiltMode()) {
13516 SetupCanvasQuiltMode();
13517 DoCanvasUpdate();
13518 InvalidateGL();
13519 Refresh();
13520 }
13521 // TODO What to do about this?
13522 // g_bQuiltEnable = GetQuiltMode();
13523
13524 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13525 if (ps52plib) ps52plib->GenerateStateHash();
13526
13527 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13528 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13529}
13530
13531void ChartCanvas::DoCanvasStackDelta(int direction) {
13532 if (!GetQuiltMode()) {
13533 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13534 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13535 if ((current_stack_index + direction) < 0) return;
13536
13537 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13538 int new_dbIndex =
13539 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13540
13541 if (IsChartQuiltableRef(new_dbIndex)) {
13542 ToggleCanvasQuiltMode();
13543 SelectQuiltRefdbChart(new_dbIndex);
13544 m_bpersistent_quilt = false;
13545 }
13546 } else {
13547 SelectChartFromStack(current_stack_index + direction);
13548 }
13549 } else {
13550 std::vector<int> piano_chart_index_array =
13551 GetQuiltExtendedStackdbIndexArray();
13552 int refdb = GetQuiltRefChartdbIndex();
13553
13554 // Find the ref chart in the stack
13555 int current_index = -1;
13556 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13557 if (refdb == piano_chart_index_array[i]) {
13558 current_index = i;
13559 break;
13560 }
13561 }
13562 if (current_index == -1) return;
13563
13564 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13565 int target_family = ctet.GetChartFamily();
13566
13567 int new_index = -1;
13568 int check_index = current_index + direction;
13569 bool found = false;
13570 int check_dbIndex = -1;
13571 int new_dbIndex = -1;
13572
13573 // When quilted. switch within the same chart family
13574 while (!found &&
13575 (unsigned int)check_index < piano_chart_index_array.size() &&
13576 (check_index >= 0)) {
13577 check_dbIndex = piano_chart_index_array[check_index];
13578 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13579 if (target_family == cte.GetChartFamily()) {
13580 found = true;
13581 new_index = check_index;
13582 new_dbIndex = check_dbIndex;
13583 break;
13584 }
13585
13586 check_index += direction;
13587 }
13588
13589 if (!found) return;
13590
13591 if (!IsChartQuiltableRef(new_dbIndex)) {
13592 ToggleCanvasQuiltMode();
13593 SelectdbChart(new_dbIndex);
13594 m_bpersistent_quilt = true;
13595 } else {
13596 SelectQuiltRefChart(new_index);
13597 }
13598 }
13599
13600 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13601 // (checkmarks etc)
13602 SetQuiltChartHiLiteIndex(-1);
13603
13604 ReloadVP();
13605}
13606
13607//--------------------------------------------------------------------------------------------------------
13608//
13609// Toolbar support
13610//
13611//--------------------------------------------------------------------------------------------------------
13612
13613void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13614 // Handle the per-canvas toolbar clicks here
13615
13616 switch (event.GetId()) {
13617 case ID_ZOOMIN: {
13618 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13619 break;
13620 }
13621
13622 case ID_ZOOMOUT: {
13623 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13624 break;
13625 }
13626
13627 case ID_STKUP:
13628 DoCanvasStackDelta(1);
13629 DoCanvasUpdate();
13630 break;
13631
13632 case ID_STKDN:
13633 DoCanvasStackDelta(-1);
13634 DoCanvasUpdate();
13635 break;
13636
13637 case ID_FOLLOW: {
13638 TogglebFollow();
13639 break;
13640 }
13641
13642 case ID_CURRENT: {
13643 ShowCurrents(!GetbShowCurrent());
13644 ReloadVP();
13645 Refresh(false);
13646 break;
13647 }
13648
13649 case ID_TIDE: {
13650 ShowTides(!GetbShowTide());
13651 ReloadVP();
13652 Refresh(false);
13653 break;
13654 }
13655
13656 case ID_ROUTE: {
13657 if (0 == m_routeState) {
13658 StartRoute();
13659 } else {
13660 FinishRoute();
13661 }
13662
13663#ifdef __ANDROID__
13664 androidSetRouteAnnunciator(m_routeState == 1);
13665#endif
13666 break;
13667 }
13668
13669 case ID_AIS: {
13670 SetAISCanvasDisplayStyle(-1);
13671 break;
13672 }
13673
13674 default:
13675 break;
13676 }
13677
13678 // And then let gFrame handle the rest....
13679 event.Skip();
13680}
13681
13682void ChartCanvas::SetShowAIS(bool show) {
13683 m_bShowAIS = show;
13684 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13685 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13686}
13687
13688void ChartCanvas::SetAttenAIS(bool show) {
13689 m_bShowAISScaled = show;
13690 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13691 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13692}
13693
13694void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
13695 // make some arrays to hold the dfferences between cycle steps
13696 // show all, scaled, hide all
13697 bool bShowAIS_Array[3] = {true, true, false};
13698 bool bShowScaled_Array[3] = {false, true, true};
13699 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
13700 _("Attenuate less critical AIS targets"),
13701 _("Hide AIS Targets")};
13702 wxString iconName_Array[3] = {_T("AIS"), _T("AIS_Suppressed"),
13703 _T("AIS_Disabled")};
13704 int ArraySize = 3;
13705 int AIS_Toolbar_Switch = 0;
13706 if (StyleIndx == -1) { // -1 means coming from toolbar button
13707 // find current state of switch
13708 for (int i = 1; i < ArraySize; i++) {
13709 if ((bShowAIS_Array[i] == m_bShowAIS) &&
13710 (bShowScaled_Array[i] == m_bShowAISScaled))
13711 AIS_Toolbar_Switch = i;
13712 }
13713 AIS_Toolbar_Switch++; // we did click so continu with next item
13714 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
13715 AIS_Toolbar_Switch++;
13716
13717 } else { // coming from menu bar.
13718 AIS_Toolbar_Switch = StyleIndx;
13719 }
13720 // make sure we are not above array
13721 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
13722
13723 int AIS_Toolbar_Switch_Next =
13724 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
13725 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
13726 AIS_Toolbar_Switch_Next++;
13727 if (AIS_Toolbar_Switch_Next >= ArraySize)
13728 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
13729
13730 // Set found values to global and member variables
13731 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
13732 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
13733}
13734
13735void ChartCanvas::TouchAISToolActive(void) {}
13736
13737void ChartCanvas::UpdateAISTBTool(void) {}
13738
13739//---------------------------------------------------------------------------------
13740//
13741// Compass/GPS status icon support
13742//
13743//---------------------------------------------------------------------------------
13744
13745void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
13746 // Look for change in overlap or positions
13747 bool b_update = false;
13748 int cc1_edge_comp = 2;
13749 wxRect rect = m_Compass->GetRect();
13750 wxSize parent_size = GetSize();
13751
13752 parent_size *= m_displayScale;
13753
13754 // check to see if it would overlap if it was in its home position (upper
13755 // right)
13756 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
13757 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
13758 wxRect compass_rect(compass_pt, rect.GetSize());
13759
13760 m_Compass->Move(compass_pt);
13761
13762 if (m_Compass && m_Compass->IsShown())
13763 m_Compass->UpdateStatus(b_force_new | b_update);
13764
13765 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
13766 scaler = wxMax(scaler, 1.0);
13767 wxPoint note_point = wxPoint(
13768 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
13769 m_notification_button->Move(note_point);
13770 m_notification_button->UpdateStatus();
13771
13772 if (b_force_new | b_update) Refresh();
13773}
13774
13775void ChartCanvas::SelectChartFromStack(int index, bool bDir,
13776 ChartTypeEnum New_Type,
13777 ChartFamilyEnum New_Family) {
13778 if (!GetpCurrentStack()) return;
13779 if (!ChartData) return;
13780
13781 if (index < GetpCurrentStack()->nEntry) {
13782 // Open the new chart
13783 ChartBase *pTentative_Chart;
13784 pTentative_Chart = ChartData->OpenStackChartConditional(
13785 GetpCurrentStack(), index, bDir, New_Type, New_Family);
13786
13787 if (pTentative_Chart) {
13788 if (m_singleChart) m_singleChart->Deactivate();
13789
13790 m_singleChart = pTentative_Chart;
13791 m_singleChart->Activate();
13792
13793 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13794 GetpCurrentStack(), m_singleChart->GetFullPath());
13795 }
13796
13797 // Setup the view
13798 double zLat, zLon;
13799 if (m_bFollow) {
13800 zLat = gLat;
13801 zLon = gLon;
13802 } else {
13803 zLat = m_vLat;
13804 zLon = m_vLon;
13805 }
13806
13807 double best_scale_ppm = GetBestVPScale(m_singleChart);
13808 double rotation = GetVPRotation();
13809 double oldskew = GetVPSkew();
13810 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
13811
13812 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
13813 if (fabs(oldskew) > 0.0001) rotation = 0.0;
13814 if (fabs(newskew) > 0.0001) rotation = newskew;
13815 }
13816
13817 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
13818
13819 UpdateGPSCompassStatusBox(true); // Pick up the rotation
13820 }
13821
13822 // refresh Piano
13823 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
13824 if (idx < 0) return;
13825
13826 std::vector<int> piano_active_chart_index_array;
13827 piano_active_chart_index_array.push_back(
13828 GetpCurrentStack()->GetCurrentEntrydbIndex());
13829 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
13830}
13831
13832void ChartCanvas::SelectdbChart(int dbindex) {
13833 if (!GetpCurrentStack()) return;
13834 if (!ChartData) return;
13835
13836 if (dbindex >= 0) {
13837 // Open the new chart
13838 ChartBase *pTentative_Chart;
13839 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
13840
13841 if (pTentative_Chart) {
13842 if (m_singleChart) m_singleChart->Deactivate();
13843
13844 m_singleChart = pTentative_Chart;
13845 m_singleChart->Activate();
13846
13847 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13848 GetpCurrentStack(), m_singleChart->GetFullPath());
13849 }
13850
13851 // Setup the view
13852 double zLat, zLon;
13853 if (m_bFollow) {
13854 zLat = gLat;
13855 zLon = gLon;
13856 } else {
13857 zLat = m_vLat;
13858 zLon = m_vLon;
13859 }
13860
13861 double best_scale_ppm = GetBestVPScale(m_singleChart);
13862
13863 if (m_singleChart)
13864 SetViewPoint(zLat, zLon, best_scale_ppm,
13865 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
13866
13867 // SetChartUpdatePeriod( );
13868
13869 // UpdateGPSCompassStatusBox(); // Pick up the rotation
13870 }
13871
13872 // TODO refresh_Piano();
13873}
13874
13875void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
13876 double target_scale = GetVP().view_scale_ppm;
13877
13878 if (!GetQuiltMode()) {
13879 if (GetpCurrentStack()) {
13880 int stack_index = -1;
13881 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
13882 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
13883 if (check_dbIndex < 0) continue;
13884 const ChartTableEntry &cte =
13885 ChartData->GetChartTableEntry(check_dbIndex);
13886 if (type == cte.GetChartType()) {
13887 stack_index = i;
13888 break;
13889 } else if (family == cte.GetChartFamily()) {
13890 stack_index = i;
13891 break;
13892 }
13893 }
13894
13895 if (stack_index >= 0) {
13896 SelectChartFromStack(stack_index);
13897 }
13898 }
13899 } else {
13900 int sel_dbIndex = -1;
13901 std::vector<int> piano_chart_index_array =
13902 GetQuiltExtendedStackdbIndexArray();
13903 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13904 int check_dbIndex = piano_chart_index_array[i];
13905 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13906 if (type == cte.GetChartType()) {
13907 if (IsChartQuiltableRef(check_dbIndex)) {
13908 sel_dbIndex = check_dbIndex;
13909 break;
13910 }
13911 } else if (family == cte.GetChartFamily()) {
13912 if (IsChartQuiltableRef(check_dbIndex)) {
13913 sel_dbIndex = check_dbIndex;
13914 break;
13915 }
13916 }
13917 }
13918
13919 if (sel_dbIndex >= 0) {
13920 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
13921 // Re-qualify the quilt reference chart selection
13922 AdjustQuiltRefChart();
13923 }
13924
13925 // Now reset the scale to the target...
13926 SetVPScale(target_scale);
13927 }
13928
13929 SetQuiltChartHiLiteIndex(-1);
13930
13931 ReloadVP();
13932}
13933
13934bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
13935 return std::find(m_tile_yesshow_index_array.begin(),
13936 m_tile_yesshow_index_array.end(),
13937 index) != m_tile_yesshow_index_array.end();
13938}
13939
13940bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
13941 return std::find(m_tile_noshow_index_array.begin(),
13942 m_tile_noshow_index_array.end(),
13943 index) != m_tile_noshow_index_array.end();
13944}
13945
13946void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
13947 if (std::find(m_tile_noshow_index_array.begin(),
13948 m_tile_noshow_index_array.end(),
13949 index) == m_tile_noshow_index_array.end()) {
13950 m_tile_noshow_index_array.push_back(index);
13951 }
13952}
13953
13954//-------------------------------------------------------------------------------------------------------
13955//
13956// Piano support
13957//
13958//-------------------------------------------------------------------------------------------------------
13959
13960void ChartCanvas::HandlePianoClick(
13961 int selected_index, const std::vector<int> &selected_dbIndex_array) {
13962 if (g_options && g_options->IsShown())
13963 return; // Piano might be invalid due to chartset updates.
13964 if (!m_pCurrentStack) return;
13965 if (!ChartData) return;
13966
13967 // stop movement or on slow computer we may get something like :
13968 // zoom out with the wheel (timer is set)
13969 // quickly click and display a chart, which may zoom in
13970 // but the delayed timer fires first and it zooms out again!
13971 StopMovement();
13972
13973 // When switching by piano key click, we may appoint the new target chart to
13974 // be any chart in the composite array.
13975 // As an improvement to UX, find the chart that is "closest" to the current
13976 // vp,
13977 // and select that chart. This will cause a jump to the centroid of that
13978 // chart
13979
13980 double distance = 25000; // RTW
13981 int closest_index = -1;
13982 for (int chart_index : selected_dbIndex_array) {
13983 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
13984 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
13985 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
13986
13987 // measure distance as Manhattan style
13988 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
13989 if (test_distance < distance) {
13990 distance = test_distance;
13991 closest_index = chart_index;
13992 }
13993 }
13994
13995 int selected_dbIndex = selected_dbIndex_array[0];
13996 if (closest_index >= 0) selected_dbIndex = closest_index;
13997
13998 if (!GetQuiltMode()) {
13999 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14000 if (IsChartQuiltableRef(selected_dbIndex)) {
14001 ToggleCanvasQuiltMode();
14002 SelectQuiltRefdbChart(selected_dbIndex);
14003 m_bpersistent_quilt = false;
14004 } else {
14005 SelectChartFromStack(selected_index);
14006 }
14007 } else {
14008 SelectChartFromStack(selected_index);
14009 g_sticky_chart = selected_dbIndex;
14010 }
14011
14012 if (m_singleChart)
14013 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14014 } else {
14015 // Handle MBTiles overlays first
14016 // Left click simply toggles the noshow array index entry
14017 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14018 bool bfound = false;
14019 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14020 if (m_tile_noshow_index_array[i] ==
14021 selected_dbIndex) { // chart is in the noshow list
14022 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14023 i); // erase it
14024 bfound = true;
14025 break;
14026 }
14027 }
14028 if (!bfound) {
14029 m_tile_noshow_index_array.push_back(selected_dbIndex);
14030 }
14031
14032 // If not already present, add this tileset to the "yes_show" array.
14033 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14034 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14035 }
14036
14037 else {
14038 if (IsChartQuiltableRef(selected_dbIndex)) {
14039 // if( ChartData ) ChartData->PurgeCache();
14040
14041 // If the chart is a vector chart, and of very large scale,
14042 // then we had better set the new scale directly to avoid excessive
14043 // underzoom on, eg, Inland ENCs
14044 bool set_scale = false;
14045 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14046 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14047 set_scale = true;
14048 }
14049 }
14050
14051 if (!set_scale) {
14052 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14053 } else {
14054 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14055
14056 // Adjust scale so that the selected chart is underzoomed/overzoomed
14057 // by a controlled amount
14058 ChartBase *pc =
14059 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14060 if (pc) {
14061 double proposed_scale_onscreen =
14063
14064 if (g_bPreserveScaleOnX) {
14065 proposed_scale_onscreen =
14066 wxMin(proposed_scale_onscreen,
14067 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14068 GetCanvasWidth()));
14069 } else {
14070 proposed_scale_onscreen =
14071 wxMin(proposed_scale_onscreen,
14072 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14073 GetCanvasWidth()));
14074
14075 proposed_scale_onscreen =
14076 wxMax(proposed_scale_onscreen,
14077 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14078 g_b_overzoom_x));
14079 }
14080
14081 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14082 }
14083 }
14084 } else {
14085 ToggleCanvasQuiltMode();
14086 SelectdbChart(selected_dbIndex);
14087 m_bpersistent_quilt = true;
14088 }
14089 }
14090 }
14091
14092 SetQuiltChartHiLiteIndex(-1);
14093 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14094 // (checkmarks etc)
14095 HideChartInfoWindow();
14096 DoCanvasUpdate();
14097 ReloadVP(); // Pick up the new selections
14098}
14099
14100void ChartCanvas::HandlePianoRClick(
14101 int x, int y, int selected_index,
14102 const std::vector<int> &selected_dbIndex_array) {
14103 if (g_options && g_options->IsShown())
14104 return; // Piano might be invalid due to chartset updates.
14105 if (!GetpCurrentStack()) return;
14106
14107 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14108 UpdateCanvasControlBar();
14109
14110 SetQuiltChartHiLiteIndex(-1);
14111}
14112
14113void ChartCanvas::HandlePianoRollover(
14114 int selected_index, const std::vector<int> &selected_dbIndex_array,
14115 int n_charts, int scale) {
14116 if (g_options && g_options->IsShown())
14117 return; // Piano might be invalid due to chartset updates.
14118 if (!GetpCurrentStack()) return;
14119 if (!ChartData) return;
14120
14121 if (ChartData->IsBusy()) return;
14122
14123 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14124
14125 if (!GetQuiltMode()) {
14126 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14127 } else {
14128 // Select the correct vector
14129 std::vector<int> piano_chart_index_array;
14130 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14131 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14132 if ((GetpCurrentStack()->nEntry > 1) ||
14133 (piano_chart_index_array.size() >= 1)) {
14134 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14135
14136 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14137 ReloadVP(false); // no VP adjustment allowed
14138 } else if (GetpCurrentStack()->nEntry == 1) {
14139 const ChartTableEntry &cte =
14140 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14141 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14142 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14143 ReloadVP(false);
14144 } else if ((-1 == selected_index) &&
14145 (0 == selected_dbIndex_array.size())) {
14146 ShowChartInfoWindow(key_location.x, -1);
14147 }
14148 }
14149 } else {
14150 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14151
14152 if ((GetpCurrentStack()->nEntry > 1) ||
14153 (piano_chart_index_array.size() >= 1)) {
14154 if (n_charts > 1)
14155 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14156 selected_dbIndex_array);
14157 else if (n_charts == 1)
14158 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14159
14160 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14161 ReloadVP(false); // no VP adjustment allowed
14162 }
14163 }
14164 }
14165}
14166
14167void ChartCanvas::ClearPianoRollover() {
14168 ClearQuiltChartHiLiteIndexArray();
14169 ShowChartInfoWindow(0, -1);
14170 std::vector<int> vec;
14171 ShowCompositeInfoWindow(0, 0, 0, vec);
14172 ReloadVP(false);
14173}
14174
14175void ChartCanvas::UpdateCanvasControlBar(void) {
14176 if (m_pianoFrozen) return;
14177
14178 if (!GetpCurrentStack()) return;
14179 if (!ChartData) return;
14180 if (!g_bShowChartBar) return;
14181
14182 int sel_type = -1;
14183 int sel_family = -1;
14184
14185 std::vector<int> piano_chart_index_array;
14186 std::vector<int> empty_piano_chart_index_array;
14187
14188 wxString old_hash = m_Piano->GetStoredHash();
14189
14190 if (GetQuiltMode()) {
14191 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14192 GetQuiltFullScreendbIndexArray());
14193
14194 std::vector<int> piano_active_chart_index_array =
14195 GetQuiltCandidatedbIndexArray();
14196 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14197
14198 std::vector<int> piano_eclipsed_chart_index_array =
14199 GetQuiltEclipsedStackdbIndexArray();
14200 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14201
14202 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14203 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14204
14205 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14206 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14207 } else {
14208 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14209 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14210 // TODO refresh_Piano();
14211
14212 if (m_singleChart) {
14213 sel_type = m_singleChart->GetChartType();
14214 sel_family = m_singleChart->GetChartFamily();
14215 }
14216 }
14217
14218 // Set up the TMerc and Skew arrays
14219 std::vector<int> piano_skew_chart_index_array;
14220 std::vector<int> piano_tmerc_chart_index_array;
14221 std::vector<int> piano_poly_chart_index_array;
14222
14223 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14224 const ChartTableEntry &ctei =
14225 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14226 double skew_norm = ctei.GetChartSkew();
14227 if (skew_norm > 180.) skew_norm -= 360.;
14228
14229 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14230 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14231
14232 // Polyconic skewed charts should show as skewed
14233 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14234 if (fabs(skew_norm) > 1.)
14235 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14236 else
14237 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14238 } else if (fabs(skew_norm) > 1.)
14239 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14240 }
14241 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14242 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14243 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14244
14245 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14246 if (new_hash != old_hash) {
14247 m_Piano->FormatKeys();
14248 HideChartInfoWindow();
14249 m_Piano->ResetRollover();
14250 SetQuiltChartHiLiteIndex(-1);
14251 m_brepaint_piano = true;
14252 }
14253
14254 // Create a bitmask int that describes what Family/Type of charts are shown in
14255 // the bar, and notify the platform.
14256 int mask = 0;
14257 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14258 const ChartTableEntry &ctei =
14259 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14260 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14261 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14262 if (e == CHART_FAMILY_RASTER) mask |= 1;
14263 if (e == CHART_FAMILY_VECTOR) {
14264 if (t == CHART_TYPE_CM93COMP)
14265 mask |= 4;
14266 else
14267 mask |= 2;
14268 }
14269 }
14270
14271 wxString s_indicated;
14272 if (sel_type == CHART_TYPE_CM93COMP)
14273 s_indicated = _T("cm93");
14274 else {
14275 if (sel_family == CHART_FAMILY_RASTER)
14276 s_indicated = _T("raster");
14277 else if (sel_family == CHART_FAMILY_VECTOR)
14278 s_indicated = _T("vector");
14279 }
14280
14281 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14282}
14283
14284void ChartCanvas::FormatPianoKeys(void) { m_Piano->FormatKeys(); }
14285
14286void ChartCanvas::PianoPopupMenu(
14287 int x, int y, int selected_index,
14288 const std::vector<int> &selected_dbIndex_array) {
14289 if (!GetpCurrentStack()) return;
14290
14291 // No context menu if quilting is disabled
14292 if (!GetQuiltMode()) return;
14293
14294 m_piano_ctx_menu = new wxMenu();
14295
14296 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14297 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14298 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14299 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14300 } else {
14301 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14302 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14303 // wxEVT_COMMAND_MENU_SELECTED,
14304 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14305
14306 menu_selected_dbIndex = selected_dbIndex_array[0];
14307 menu_selected_index = selected_index;
14308
14309 // Search the no-show array
14310 bool b_is_in_noshow = false;
14311 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14312 if (m_quilt_noshow_index_array[i] ==
14313 menu_selected_dbIndex) // chart is in the noshow list
14314 {
14315 b_is_in_noshow = true;
14316 break;
14317 }
14318 }
14319
14320 if (b_is_in_noshow) {
14321 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14322 _("Show This Chart"));
14323 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14324 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14325 } else if (GetpCurrentStack()->nEntry > 1) {
14326 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14327 _("Hide This Chart"));
14328 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14329 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14330 }
14331 }
14332
14333 wxPoint pos = wxPoint(x, y - 30);
14334
14335 // Invoke the drop-down menu
14336 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14337 PopupMenu(m_piano_ctx_menu, pos);
14338
14339 delete m_piano_ctx_menu;
14340 m_piano_ctx_menu = NULL;
14341
14342 HideChartInfoWindow();
14343 m_Piano->ResetRollover();
14344
14345 SetQuiltChartHiLiteIndex(-1);
14346 ClearQuiltChartHiLiteIndexArray();
14347
14348 ReloadVP();
14349}
14350
14351void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14352 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14353 if (m_quilt_noshow_index_array[i] ==
14354 menu_selected_dbIndex) // chart is in the noshow list
14355 {
14356 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14357 break;
14358 }
14359 }
14360}
14361
14362void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14363 if (!GetpCurrentStack()) return;
14364 if (!ChartData) return;
14365
14366 RemoveChartFromQuilt(menu_selected_dbIndex);
14367
14368 // It could happen that the chart being disabled is the reference
14369 // chart....
14370 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14371 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14372
14373 int i = menu_selected_index + 1; // select next smaller scale chart
14374 bool b_success = false;
14375 while (i < GetpCurrentStack()->nEntry - 1) {
14376 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14377 if (type == ChartData->GetDBChartType(dbIndex)) {
14378 SelectQuiltRefChart(i);
14379 b_success = true;
14380 break;
14381 }
14382 i++;
14383 }
14384
14385 // If that did not work, try to select the next larger scale compatible
14386 // chart
14387 if (!b_success) {
14388 i = menu_selected_index - 1;
14389 while (i > 0) {
14390 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14391 if (type == ChartData->GetDBChartType(dbIndex)) {
14392 SelectQuiltRefChart(i);
14393 b_success = true;
14394 break;
14395 }
14396 i--;
14397 }
14398 }
14399 }
14400}
14401
14402void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14403 // Remove the item from the list (if it appears) to avoid multiple addition
14404 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14405 if (m_quilt_noshow_index_array[i] ==
14406 dbIndex) // chart is already in the noshow list
14407 {
14408 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14409 break;
14410 }
14411 }
14412
14413 m_quilt_noshow_index_array.push_back(dbIndex);
14414}
14415
14416bool ChartCanvas::UpdateS52State() {
14417 bool retval = false;
14418 // printf(" update %d\n", IsPrimaryCanvas());
14419
14420 if (ps52plib) {
14421 ps52plib->SetShowS57Text(m_encShowText);
14422 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14423 ps52plib->m_bShowSoundg = m_encShowDepth;
14424 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14425 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14426
14427 // Lights
14428 if (!m_encShowLights) // On, going off
14429 ps52plib->AddObjNoshow("LIGHTS");
14430 else // Off, going on
14431 ps52plib->RemoveObjNoshow("LIGHTS");
14432 ps52plib->SetLightsOff(!m_encShowLights);
14433 ps52plib->m_bExtendLightSectors = true;
14434
14435 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14436 ps52plib->SetAnchorOn(m_encShowAnchor);
14437 ps52plib->SetQualityOfData(m_encShowDataQual);
14438 }
14439
14440 return retval;
14441}
14442
14443void ChartCanvas::SetShowENCDataQual(bool show) {
14444 m_encShowDataQual = show;
14445 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14446 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14447
14448 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14449}
14450
14451void ChartCanvas::SetShowENCText(bool show) {
14452 m_encShowText = show;
14453 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14454 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14455
14456 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14457}
14458
14459void ChartCanvas::SetENCDisplayCategory(int category) {
14460 m_encDisplayCategory = category;
14461 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14462}
14463
14464void ChartCanvas::SetShowENCDepth(bool show) {
14465 m_encShowDepth = show;
14466 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14467 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14468
14469 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14470}
14471
14472void ChartCanvas::SetShowENCLightDesc(bool show) {
14473 m_encShowLightDesc = show;
14474 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14475 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14476
14477 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14478}
14479
14480void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14481 m_encShowBuoyLabels = show;
14482 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14483}
14484
14485void ChartCanvas::SetShowENCLights(bool show) {
14486 m_encShowLights = show;
14487 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14488 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14489
14490 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14491}
14492
14493void ChartCanvas::SetShowENCAnchor(bool show) {
14494 m_encShowAnchor = show;
14495 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14496 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14497
14498 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14499}
14500
14501wxRect ChartCanvas::GetMUIBarRect() {
14502 wxRect rv;
14503 if (m_muiBar) {
14504 rv = m_muiBar->GetRect();
14505 }
14506
14507 return rv;
14508}
14509
14510void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14511 if (!GetAlertString().IsEmpty()) {
14512 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14513 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14514
14515 dc.SetFont(*pfont);
14516 dc.SetPen(*wxTRANSPARENT_PEN);
14517
14518 dc.SetBrush(wxColour(243, 229, 47));
14519 int w, h;
14520 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14521 h += 2;
14522 // int yp = vp.pix_height - 20 - h;
14523
14524 wxRect sbr = GetScaleBarRect();
14525 int xp = sbr.x + sbr.width + 10;
14526 int yp = (sbr.y + sbr.height) - h;
14527
14528 int wdraw = w + 10;
14529 dc.DrawRectangle(xp, yp, wdraw, h);
14530 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14531 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14532 }
14533}
14534
14535//--------------------------------------------------------------------------------------------------------
14536// Screen Brightness Control Support Routines
14537//
14538//--------------------------------------------------------------------------------------------------------
14539
14540#ifdef __UNIX__
14541#define BRIGHT_XCALIB
14542#define __OPCPN_USEICC__
14543#endif
14544
14545#ifdef __OPCPN_USEICC__
14546int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14547 double co_green, double co_blue);
14548
14549wxString temp_file_name;
14550#endif
14551
14552#if 0
14553class ocpnCurtain: public wxDialog
14554{
14555 DECLARE_CLASS( ocpnCurtain )
14556 DECLARE_EVENT_TABLE()
14557
14558public:
14559 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14560 ~ocpnCurtain( );
14561 bool ProcessEvent(wxEvent& event);
14562
14563};
14564
14565IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14566
14567BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14568END_EVENT_TABLE()
14569
14570ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14571{
14572 wxDialog::Create( parent, -1, _T("ocpnCurtain"), position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14573}
14574
14575ocpnCurtain::~ocpnCurtain()
14576{
14577}
14578
14579bool ocpnCurtain::ProcessEvent(wxEvent& event)
14580{
14581 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14582 return GetParent()->GetEventHandler()->ProcessEvent(event);
14583}
14584#endif
14585
14586#ifdef _WIN32
14587#include <windows.h>
14588
14589HMODULE hGDI32DLL;
14590typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14591typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14592SetDeviceGammaRamp_ptr_type
14593 g_pSetDeviceGammaRamp; // the API entry points in the dll
14594GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14595
14596WORD *g_pSavedGammaMap;
14597
14598#endif
14599
14600int InitScreenBrightness(void) {
14601#ifdef _WIN32
14602#ifdef ocpnUSE_GL
14603 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14604 HDC hDC;
14605 BOOL bbr;
14606
14607 if (NULL == hGDI32DLL) {
14608 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14609
14610 if (NULL != hGDI32DLL) {
14611 // Get the entry points of the required functions
14612 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14613 hGDI32DLL, "SetDeviceGammaRamp");
14614 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14615 hGDI32DLL, "GetDeviceGammaRamp");
14616
14617 // If the functions are not found, unload the DLL and return false
14618 if ((NULL == g_pSetDeviceGammaRamp) ||
14619 (NULL == g_pGetDeviceGammaRamp)) {
14620 FreeLibrary(hGDI32DLL);
14621 hGDI32DLL = NULL;
14622 return 0;
14623 }
14624 }
14625 }
14626
14627 // Interface is ready, so....
14628 // Get some storage
14629 if (!g_pSavedGammaMap) {
14630 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14631
14632 hDC = GetDC(NULL); // Get the full screen DC
14633 bbr = g_pGetDeviceGammaRamp(
14634 hDC, g_pSavedGammaMap); // Get the existing ramp table
14635 ReleaseDC(NULL, hDC); // Release the DC
14636 }
14637
14638 // On Windows hosts, try to adjust the registry to allow full range
14639 // setting of Gamma table This is an undocumented Windows hack.....
14640 wxRegKey *pRegKey = new wxRegKey(
14641 _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows ")
14642 _T("NT\\CurrentVersion\\ICM"));
14643 if (!pRegKey->Exists()) pRegKey->Create();
14644 pRegKey->SetValue(_T("GdiIcmGammaRange"), 256);
14645
14646 g_brightness_init = true;
14647 return 1;
14648 }
14649#endif
14650
14651 {
14652 if (NULL == g_pcurtain) {
14653 if (gFrame->CanSetTransparent()) {
14654 // Build the curtain window
14655 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, _T(""),
14656 wxPoint(0, 0), ::wxGetDisplaySize(),
14657 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14658 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14659
14660 // g_pcurtain = new ocpnCurtain(gFrame,
14661 // wxPoint(0,0),::wxGetDisplaySize(),
14662 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14663 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14664
14665 g_pcurtain->Hide();
14666
14667 HWND hWnd = GetHwndOf(g_pcurtain);
14668 SetWindowLong(hWnd, GWL_EXSTYLE,
14669 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14670 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14671 g_pcurtain->SetTransparent(0);
14672
14673 g_pcurtain->Maximize();
14674 g_pcurtain->Show();
14675
14676 // All of this is obtuse, but necessary for Windows...
14677 g_pcurtain->Enable();
14678 g_pcurtain->Disable();
14679
14680 gFrame->Disable();
14681 gFrame->Enable();
14682 // SetFocus();
14683 }
14684 }
14685 g_brightness_init = true;
14686
14687 return 1;
14688 }
14689#else
14690 // Look for "xcalib" application
14691 wxString cmd(_T ( "xcalib -version" ));
14692
14693 wxArrayString output;
14694 long r = wxExecute(cmd, output);
14695 if (0 != r)
14696 wxLogMessage(
14697 _T(" External application \"xcalib\" not found. Screen brightness ")
14698 _T("not changed."));
14699
14700 g_brightness_init = true;
14701 return 0;
14702#endif
14703}
14704
14705int RestoreScreenBrightness(void) {
14706#ifdef _WIN32
14707
14708 if (g_pSavedGammaMap) {
14709 HDC hDC = GetDC(NULL); // Get the full screen DC
14710 g_pSetDeviceGammaRamp(hDC,
14711 g_pSavedGammaMap); // Restore the saved ramp table
14712 ReleaseDC(NULL, hDC); // Release the DC
14713
14714 free(g_pSavedGammaMap);
14715 g_pSavedGammaMap = NULL;
14716 }
14717
14718 if (g_pcurtain) {
14719 g_pcurtain->Close();
14720 g_pcurtain->Destroy();
14721 g_pcurtain = NULL;
14722 }
14723
14724 g_brightness_init = false;
14725 return 1;
14726
14727#endif
14728
14729#ifdef BRIGHT_XCALIB
14730 if (g_brightness_init) {
14731 wxString cmd;
14732 cmd = _T("xcalib -clear");
14733 wxExecute(cmd, wxEXEC_ASYNC);
14734 g_brightness_init = false;
14735 }
14736
14737 return 1;
14738#endif
14739
14740 return 0;
14741}
14742
14743// Set brightness. [0..100]
14744int SetScreenBrightness(int brightness) {
14745#ifdef _WIN32
14746
14747 // Under Windows, we use the SetDeviceGammaRamp function which exists in
14748 // some (most modern?) versions of gdi32.dll Load the required library dll,
14749 // if not already in place
14750#ifdef ocpnUSE_GL
14751 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14752 if (g_pcurtain) {
14753 g_pcurtain->Close();
14754 g_pcurtain->Destroy();
14755 g_pcurtain = NULL;
14756 }
14757
14758 InitScreenBrightness();
14759
14760 if (NULL == hGDI32DLL) {
14761 // Unicode stuff.....
14762 wchar_t wdll_name[80];
14763 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
14764 LPCWSTR cstr = wdll_name;
14765
14766 hGDI32DLL = LoadLibrary(cstr);
14767
14768 if (NULL != hGDI32DLL) {
14769 // Get the entry points of the required functions
14770 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14771 hGDI32DLL, "SetDeviceGammaRamp");
14772 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14773 hGDI32DLL, "GetDeviceGammaRamp");
14774
14775 // If the functions are not found, unload the DLL and return false
14776 if ((NULL == g_pSetDeviceGammaRamp) ||
14777 (NULL == g_pGetDeviceGammaRamp)) {
14778 FreeLibrary(hGDI32DLL);
14779 hGDI32DLL = NULL;
14780 return 0;
14781 }
14782 }
14783 }
14784
14785 HDC hDC = GetDC(NULL); // Get the full screen DC
14786
14787 /*
14788 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
14789 if (cmcap != CM_GAMMA_RAMP)
14790 {
14791 wxLogMessage(_T(" Video hardware does not support brightness control by
14792 gamma ramp adjustment.")); return false;
14793 }
14794 */
14795
14796 int increment = brightness * 256 / 100;
14797
14798 // Build the Gamma Ramp table
14799 WORD GammaTable[3][256];
14800
14801 int table_val = 0;
14802 for (int i = 0; i < 256; i++) {
14803 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
14804 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
14805 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
14806
14807 table_val += increment;
14808
14809 if (table_val > 65535) table_val = 65535;
14810 }
14811
14812 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
14813 ReleaseDC(NULL, hDC); // Release the DC
14814
14815 return 1;
14816 }
14817#endif
14818
14819 {
14820 if (g_pSavedGammaMap) {
14821 HDC hDC = GetDC(NULL); // Get the full screen DC
14822 g_pSetDeviceGammaRamp(hDC,
14823 g_pSavedGammaMap); // Restore the saved ramp table
14824 ReleaseDC(NULL, hDC); // Release the DC
14825 }
14826
14827 if (brightness < 100) {
14828 if (NULL == g_pcurtain) InitScreenBrightness();
14829
14830 if (g_pcurtain) {
14831 int sbrite = wxMax(1, brightness);
14832 sbrite = wxMin(100, sbrite);
14833
14834 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
14835 }
14836 } else {
14837 if (g_pcurtain) {
14838 g_pcurtain->Close();
14839 g_pcurtain->Destroy();
14840 g_pcurtain = NULL;
14841 }
14842 }
14843
14844 return 1;
14845 }
14846
14847#endif
14848
14849#ifdef BRIGHT_XCALIB
14850
14851 if (!g_brightness_init) {
14852 last_brightness = 100;
14853 g_brightness_init = true;
14854 temp_file_name = wxFileName::CreateTempFileName(_T(""));
14855 InitScreenBrightness();
14856 }
14857
14858#ifdef __OPCPN_USEICC__
14859 // Create a dead simple temporary ICC profile file, with gamma ramps set as
14860 // desired, and then activate this temporary profile using xcalib <filename>
14861 if (!CreateSimpleICCProfileFile(
14862 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
14863 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
14864 wxString cmd(_T ( "xcalib " ));
14865 cmd += temp_file_name;
14866
14867 wxExecute(cmd, wxEXEC_ASYNC);
14868 }
14869
14870#else
14871 // Or, use "xcalib -co" to set overall contrast value
14872 // This is not as nice, since the -co parameter wants to be a fraction of
14873 // the current contrast, and values greater than 100 are not allowed. As a
14874 // result, increases of contrast must do a "-clear" step first, which
14875 // produces objectionable flashing.
14876 if (brightness > last_brightness) {
14877 wxString cmd;
14878 cmd = _T("xcalib -clear");
14879 wxExecute(cmd, wxEXEC_ASYNC);
14880
14881 ::wxMilliSleep(10);
14882
14883 int brite_adj = wxMax(1, brightness);
14884 cmd.Printf(_T("xcalib -co %2d -a"), brite_adj);
14885 wxExecute(cmd, wxEXEC_ASYNC);
14886 } else {
14887 int brite_adj = wxMax(1, brightness);
14888 int factor = (brite_adj * 100) / last_brightness;
14889 factor = wxMax(1, factor);
14890 wxString cmd;
14891 cmd.Printf(_T("xcalib -co %2d -a"), factor);
14892 wxExecute(cmd, wxEXEC_ASYNC);
14893 }
14894
14895#endif
14896
14897 last_brightness = brightness;
14898
14899#endif
14900
14901 return 0;
14902}
14903
14904#ifdef __OPCPN_USEICC__
14905
14906#define MLUT_TAG 0x6d4c5554L
14907#define VCGT_TAG 0x76636774L
14908
14909int GetIntEndian(unsigned char *s) {
14910 int ret;
14911 unsigned char *p;
14912 int i;
14913
14914 p = (unsigned char *)&ret;
14915
14916 if (1)
14917 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
14918 else
14919 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
14920
14921 return ret;
14922}
14923
14924unsigned short GetShortEndian(unsigned char *s) {
14925 unsigned short ret;
14926 unsigned char *p;
14927 int i;
14928
14929 p = (unsigned char *)&ret;
14930
14931 if (1)
14932 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
14933 else
14934 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
14935
14936 return ret;
14937}
14938
14939// Create a very simple Gamma correction file readable by xcalib
14940int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14941 double co_green, double co_blue) {
14942 FILE *fp;
14943
14944 if (file_name) {
14945 fp = fopen(file_name, "wb");
14946 if (!fp) return -1; /* file can not be created */
14947 } else
14948 return -1; /* filename char pointer not valid */
14949
14950 // Write header
14951 char header[128];
14952 for (int i = 0; i < 128; i++) header[i] = 0;
14953
14954 fwrite(header, 128, 1, fp);
14955
14956 // Num tags
14957 int numTags0 = 1;
14958 int numTags = GetIntEndian((unsigned char *)&numTags0);
14959 fwrite(&numTags, 1, 4, fp);
14960
14961 int tagName0 = VCGT_TAG;
14962 int tagName = GetIntEndian((unsigned char *)&tagName0);
14963 fwrite(&tagName, 1, 4, fp);
14964
14965 int tagOffset0 = 128 + 4 * sizeof(int);
14966 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
14967 fwrite(&tagOffset, 1, 4, fp);
14968
14969 int tagSize0 = 1;
14970 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
14971 fwrite(&tagSize, 1, 4, fp);
14972
14973 fwrite(&tagName, 1, 4, fp); // another copy of tag
14974
14975 fwrite(&tagName, 1, 4, fp); // dummy
14976
14977 // Table type
14978
14979 /* VideoCardGammaTable (The simplest type) */
14980 int gammatype0 = 0;
14981 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
14982 fwrite(&gammatype, 1, 4, fp);
14983
14984 int numChannels0 = 3;
14985 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
14986 fwrite(&numChannels, 1, 2, fp);
14987
14988 int numEntries0 = 256;
14989 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
14990 fwrite(&numEntries, 1, 2, fp);
14991
14992 int entrySize0 = 1;
14993 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
14994 fwrite(&entrySize, 1, 2, fp);
14995
14996 unsigned char ramp[256];
14997
14998 // Red ramp
14999 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15000 fwrite(ramp, 256, 1, fp);
15001
15002 // Green ramp
15003 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15004 fwrite(ramp, 256, 1, fp);
15005
15006 // Blue ramp
15007 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15008 fwrite(ramp, 256, 1, fp);
15009
15010 fclose(fp);
15011
15012 return 0;
15013}
15014#endif // __OPCPN_USEICC__
bool g_bresponsive
Flag to control adaptive UI scaling.
Definition ocpn_app.cpp:662
Global state for AIS decoder.
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.
Represents an active track that is currently being recorded.
Definition track.h:221
Handles context menu events for the chart canvas.
Definition canvasMenu.h:82
A custom panel for displaying chart information.
Definition ChInfoWin.h:36
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:131
Base class for all chart types.
Definition chartbase.h:119
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:151
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:4571
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4567
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3666
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:4517
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:745
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:475
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:506
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2403
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7573
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5070
float GetVPScale()
Return the ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:464
double GetCanvasTrueScale()
Return the physical pixels per meter at chart center, accounting for latitude distortion.
Definition chcanv.h:480
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4648
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5349
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:729
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4592
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:4654
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4512
bool SetViewPoint(double lat, double lon)
Set the viewport center point.
Definition chcanv.cpp:5368
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:9921
Manages the chart database and provides access to chart data.
Definition chartdb.h:95
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:464
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:392
Primary navigation console display for route and vessel tracking.
Definition concanv.h:127
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:467
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:203
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:212
Main application frame.
Definition ocpn_frame.h:136
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.
Provides platform-specific support utilities for OpenCPN.
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
A popup frame containing a detail slider for chart display.
Definition Quilt.h:83
bool Compose(const ViewPort &vp)
Definition Quilt.cpp:1709
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
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:194
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:419
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:862
Dialog for displaying query results of S57 objects.
Manager for S57 chart SENC creation threads.
Manages a set of ShapeBaseChart objects at different resolutions.
Definition tcmgr.h:88
Definition TCWin.h:46
Window for displaying chart thumbnails.
Definition thumbwin.h:56
Represents a single point in a track.
Definition track.h:53
wxDateTime GetCreateTime(void)
Retrieves the creation timestamp of a track point as a wxDateTime object.
Definition track.cpp:140
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:146
Class TrackPropDlg.
bool UpdateProperties()
Represents a track, which is a series of connected track points.
Definition track.h:111
Definition undo.h:60
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:81
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:229
double ref_scale
The nominal scale of the "reference chart" for this view.
Definition viewport.h:246
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:258
void SetBoxes(void)
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp:823
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:239
void SetPixelScale(double scale)
Set the physical to logical pixel ratio for the display.
Definition viewport.cpp:133
int pix_width
Width of the viewport in physical pixels.
Definition viewport.h:256
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:145
double tilt
Tilt angle for perspective view in radians.
Definition viewport.h:241
double skew
Angular distortion (shear transform) applied to the viewport in radians.
Definition viewport.h:237
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:105
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:224
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:222
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:136
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:244
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:424
Stores emboss effect data for textures.
Definition emboss_data.h:35
OpenGL chart rendering canvas.
Floating toolbar for iENC (International Electronic Navigational Chart) functionality.
Definition iENCToolbar.h:43
Represents a compass display in the OpenCPN navigation system.
Definition compass.h:37
wxRect GetRect(void) const
Return the coordinates of the compass widget, in physical pixels relative to the canvas window.
Definition compass.h:61
wxRect GetLogicalRect(void) const
Return the coordinates of the compass widget, in logical pixels.
Definition compass.cpp:201
Device context class that can use either wxDC or OpenGL for drawing.
Definition ocpndc.h:64
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
Floating toolbar dialog for OpenCPN.
Definition toolbar.h:386
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:120
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.
Global variables reflecting command line options and arguments.
Hooks into gui available in model.
Class NavObj_dB.
Class NotificationManager.
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.
double OCPN_GetWinDIPScaleFactor()
Gets Windows-specific DPI scaling factor.
Tools to send data to plugins.
Route validators for dialog validation.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:181
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.