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 int gridFontSize = 8;
793#if defined(__WXOSX__) || defined(__WXGTK3__)
794 // Support scaled HDPI displays.
795 gridFontSize *= GetContentScaleFactor();
796#endif
797
798 m_pgridFont = FontMgr::Get().FindOrCreateFont(
799 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
800 FALSE, wxString(_T ( "Arial" )));
801
802 m_Piano = new Piano(this);
803
804 m_bShowCompassWin = true;
805
806 m_Compass = new ocpnCompass(this);
807 m_Compass->SetScaleFactor(g_compass_scalefactor);
808 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
809
810 m_notification_button = new NotificationButton(this);
811 m_notification_button->SetScaleFactor(g_compass_scalefactor);
812 m_notification_button->Show(true);
813
814 m_pianoFrozen = false;
815
816 SetMinSize(wxSize(200, 200));
817
818 m_displayScale = 1.0;
819#if defined(__WXOSX__) || defined(__WXGTK3__)
820 // Support scaled HDPI displays.
821 m_displayScale = GetContentScaleFactor();
822#endif
823 VPoint.SetPixelScale(m_displayScale);
824
825#ifdef HAVE_WX_GESTURE_EVENTS
826 // if (!m_glcc)
827 {
828 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
829 wxLogError("Failed to enable touch events");
830 }
831
832 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
833
834 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
835 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
836
837 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
838 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
839
840 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
841 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
842
843 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
844 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
845 }
846#endif
847
848 // Listen for notification events
849 auto &noteman = NotificationManager::GetInstance();
850
851 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
852 evt_notificationlist_change_listener.Listen(
853 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
854 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
855 if (m_NotificationsList && m_NotificationsList->IsShown()) {
856 m_NotificationsList->ReloadNotificationList();
857 }
858 Refresh();
859 });
860}
861
862ChartCanvas::~ChartCanvas() {
863 delete pThumbDIBShow;
864
865 // Delete Cursors
866 delete pCursorLeft;
867 delete pCursorRight;
868 delete pCursorUp;
869 delete pCursorDown;
870 delete pCursorArrow;
871 delete pCursorPencil;
872 delete pCursorCross;
873
874 delete pPanTimer;
875 delete pMovementTimer;
876 delete pMovementStopTimer;
877 delete pCurTrackTimer;
878 delete pRotDefTimer;
879 delete m_DoubleClickTimer;
880
881 delete m_pTrackRolloverWin;
882 delete m_pRouteRolloverWin;
883 delete m_pAISRolloverWin;
884 delete m_pBrightPopup;
885
886 delete m_pCIWin;
887
888 delete pscratch_bm;
889
890 m_dc_route.SelectObject(wxNullBitmap);
891 delete proute_bm;
892
893 delete pWorldBackgroundChart;
894 delete pss_overlay_bmp;
895
896 delete m_pEM_Feet;
897 delete m_pEM_Meters;
898 delete m_pEM_Fathoms;
899
900 delete m_pEM_OverZoom;
901 // delete m_pEM_CM93Offset;
902
903 delete m_prot_bm;
904
905 delete m_pos_image_user_day;
906 delete m_pos_image_user_dusk;
907 delete m_pos_image_user_night;
908 delete m_pos_image_user_grey_day;
909 delete m_pos_image_user_grey_dusk;
910 delete m_pos_image_user_grey_night;
911 delete m_pos_image_user_yellow_day;
912 delete m_pos_image_user_yellow_dusk;
913 delete m_pos_image_user_yellow_night;
914
915 delete undo;
916#ifdef ocpnUSE_GL
917 if (!g_bdisable_opengl) {
918 delete m_glcc;
919
920#if wxCHECK_VERSION(2, 9, 0)
921 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
922#endif
923 }
924#endif
925
926 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
927 // wx tries to deliver events to this canvas during destroy.
928 MUIBar *muiBar = m_muiBar;
929 m_muiBar = 0;
930 delete muiBar;
931 delete m_pQuilt;
932 delete m_pCurrentStack;
933 delete m_Compass;
934 delete m_Piano;
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
3840#ifdef ocpnUSE_GL
3841 if (g_bopengl && m_glcc) {
3842 m_glcc->SetColorScheme(cs);
3843 g_glTextureManager->ClearAllRasterTextures();
3844 // m_glcc->FlushFBO();
3845 }
3846#endif
3847 SetbTCUpdate(true); // force re-render of tide/current locators
3848 m_brepaint_piano = true;
3849
3850 ReloadVP();
3851
3852 m_cs = cs;
3853}
3854
3855wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3856 wxImage img = Bitmap.ConvertToImage();
3857 int sx = img.GetWidth();
3858 int sy = img.GetHeight();
3859
3860 wxImage new_img(img);
3861
3862 for (int i = 0; i < sx; i++) {
3863 for (int j = 0; j < sy; j++) {
3864 if (!img.IsTransparent(i, j)) {
3865 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3866 (unsigned char)(img.GetGreen(i, j) * factor),
3867 (unsigned char)(img.GetBlue(i, j) * factor));
3868 }
3869 }
3870 }
3871
3872 wxBitmap ret = wxBitmap(new_img);
3873
3874 return ret;
3875}
3876
3877void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3878 int max) {
3879 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3880 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3881
3882 if (!m_pBrightPopup) {
3883 // Calculate size
3884 int x, y;
3885 GetTextExtent(_T("MAX"), &x, &y, NULL, NULL, pfont);
3886
3887 m_pBrightPopup = new TimedPopupWin(this, 3);
3888
3889 m_pBrightPopup->SetSize(x, y);
3890 m_pBrightPopup->Move(120, 120);
3891 }
3892
3893 int bmpsx = m_pBrightPopup->GetSize().x;
3894 int bmpsy = m_pBrightPopup->GetSize().y;
3895
3896 wxBitmap bmp(bmpsx, bmpsx);
3897 wxMemoryDC mdc(bmp);
3898
3899 mdc.SetTextForeground(GetGlobalColor(_T("GREEN4")));
3900 mdc.SetBackground(wxBrush(GetGlobalColor(_T("UINFD"))));
3901 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3902 mdc.SetBrush(wxBrush(GetGlobalColor(_T("UINFD"))));
3903 mdc.Clear();
3904
3905 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3906
3907 mdc.SetFont(*pfont);
3908 wxString val;
3909
3910 if (brightness == max)
3911 val = _T("MAX");
3912 else if (brightness == min)
3913 val = _T("MIN");
3914 else
3915 val.Printf(_T("%3d"), brightness);
3916
3917 mdc.DrawText(val, 0, 0);
3918
3919 mdc.SelectObject(wxNullBitmap);
3920
3921 m_pBrightPopup->SetBitmap(bmp);
3922 m_pBrightPopup->Show();
3923 m_pBrightPopup->Refresh();
3924}
3925
3926void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3927 m_b_rot_hidef = true;
3928 ReloadVP();
3929}
3930
3931void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3932 if (!g_bRollover) return;
3933
3934 bool b_need_refresh = false;
3935
3936 wxSize win_size = GetSize() * m_displayScale;
3937 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3938
3939 // Handle the AIS Rollover Window first
3940 bool showAISRollover = false;
3941 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3942 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3943 SelectItem *pFind = pSelectAIS->FindSelection(
3944 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3945 if (pFind) {
3946 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3947 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3948
3949 if (ptarget) {
3950 showAISRollover = true;
3951
3952 if (NULL == m_pAISRolloverWin) {
3953 m_pAISRolloverWin = new RolloverWin(this);
3954 m_pAISRolloverWin->IsActive(false);
3955 b_need_refresh = true;
3956 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3957 m_AISRollover_MMSI != FoundAIS_MMSI) {
3958 // Sometimes the mouse moves fast enough to get over a new AIS
3959 // target before the one-shot has fired to remove the old target.
3960 // Result: wrong target data is shown.
3961 // Detect this case,close the existing rollover ASAP, and restart
3962 // the timer.
3963 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3964 m_pAISRolloverWin->IsActive(false);
3965 m_AISRollover_MMSI = 0;
3966 Refresh();
3967 return;
3968 }
3969
3970 m_AISRollover_MMSI = FoundAIS_MMSI;
3971
3972 if (!m_pAISRolloverWin->IsActive()) {
3973 wxString s = ptarget->GetRolloverString();
3974 m_pAISRolloverWin->SetString(s);
3975
3976 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3977 AIS_ROLLOVER, win_size);
3978 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3979 m_pAISRolloverWin->IsActive(true);
3980 b_need_refresh = true;
3981 }
3982 }
3983 } else {
3984 m_AISRollover_MMSI = 0;
3985 showAISRollover = false;
3986 }
3987 }
3988
3989 // Maybe turn the rollover off
3990 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3991 m_pAISRolloverWin->IsActive(false);
3992 m_AISRollover_MMSI = 0;
3993 b_need_refresh = true;
3994 }
3995
3996 // Now the Route info rollover
3997 // Show the route segment info
3998 bool showRouteRollover = false;
3999
4000 if (NULL == m_pRolloverRouteSeg) {
4001 // Get a list of all selectable sgements, and search for the first
4002 // visible segment as the rollover target.
4003
4004 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4005 SelectableItemList SelList = pSelect->FindSelectionList(
4006 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
4007 wxSelectableItemListNode *node = SelList.GetFirst();
4008 while (node) {
4009 SelectItem *pFindSel = node->GetData();
4010
4011 Route *pr = (Route *)pFindSel->m_pData3; // candidate
4012
4013 if (pr && pr->IsVisible()) {
4014 m_pRolloverRouteSeg = pFindSel;
4015 showRouteRollover = true;
4016
4017 if (NULL == m_pRouteRolloverWin) {
4018 m_pRouteRolloverWin = new RolloverWin(this, 10);
4019 m_pRouteRolloverWin->IsActive(false);
4020 }
4021
4022 if (!m_pRouteRolloverWin->IsActive()) {
4023 wxString s;
4024 RoutePoint *segShow_point_a =
4025 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4026 RoutePoint *segShow_point_b =
4027 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4028
4029 double brg, dist;
4030 DistanceBearingMercator(
4031 segShow_point_b->m_lat, segShow_point_b->m_lon,
4032 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4033
4034 if (!pr->m_bIsInLayer)
4035 s.Append(_("Route") + _T(": "));
4036 else
4037 s.Append(_("Layer Route: "));
4038
4039 if (pr->m_RouteNameString.IsEmpty())
4040 s.Append(_("(unnamed)"));
4041 else
4042 s.Append(pr->m_RouteNameString);
4043
4044 s << _T("\n") << _("Total Length: ")
4045 << FormatDistanceAdaptive(pr->m_route_length) << _T("\n")
4046 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4047 << segShow_point_b->GetName() << _T("\n");
4048
4049 if (g_bShowTrue)
4050 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4051 (int)floor(brg + 0.5), 0x00B0);
4052 if (g_bShowMag) {
4053 double latAverage =
4054 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4055 double lonAverage =
4056 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4057 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4058
4059 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4060 (int)floor(varBrg + 0.5), 0x00B0);
4061 }
4062
4063 s << FormatDistanceAdaptive(dist);
4064
4065 // Compute and display cumulative distance from route start point to
4066 // current leg end point and RNG,TTG,ETA from ship to current leg end
4067 // point for active route
4068 double shiptoEndLeg = 0.;
4069 bool validActive = false;
4070 if (pr->IsActive() &&
4071 pr->pRoutePointList->GetFirst()->GetData()->m_bIsActive)
4072 validActive = true;
4073
4074 if (segShow_point_a != pr->pRoutePointList->GetFirst()->GetData()) {
4075 wxRoutePointListNode *node =
4076 (pr->pRoutePointList)->GetFirst()->GetNext();
4077 RoutePoint *prp;
4078 float dist_to_endleg = 0;
4079 wxString t;
4080
4081 while (node) {
4082 prp = node->GetData();
4083 if (validActive)
4084 shiptoEndLeg += prp->m_seg_len;
4085 else if (prp->m_bIsActive)
4086 validActive = true;
4087 dist_to_endleg += prp->m_seg_len;
4088 if (prp->IsSame(segShow_point_a)) break;
4089 node = node->GetNext();
4090 }
4091 s << _T(" (+") << FormatDistanceAdaptive(dist_to_endleg) << _T(")");
4092 }
4093 // write from ship to end selected leg point data if the route is
4094 // active
4095 if (validActive) {
4096 s << _T("\n") << _("From Ship To") << _T(" ")
4097 << segShow_point_b->GetName() << _T("\n");
4098 shiptoEndLeg +=
4099 g_pRouteMan
4100 ->GetCurrentRngToActivePoint(); // add distance from ship
4101 // to active point
4102 shiptoEndLeg +=
4103 segShow_point_b
4104 ->m_seg_len; // add the lenght of the selected leg
4105 s << FormatDistanceAdaptive(shiptoEndLeg);
4106 // ensure sog/cog are valid and vmg is positive to keep data
4107 // coherent
4108 double vmg = 0.;
4109 if (!std::isnan(gCog) && !std::isnan(gSog))
4110 vmg = gSog *
4111 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4112 PI / 180.);
4113 if (vmg > 0.) {
4114 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4115 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4116 s << _T(" - ")
4117 << wxString(ttg_sec > SECONDS_PER_DAY
4118 ? ttg_span.Format(_("%Dd %H:%M"))
4119 : ttg_span.Format(_("%H:%M")));
4120 wxDateTime dtnow, eta;
4121 eta = dtnow.SetToCurrent().Add(ttg_span);
4122 s << _T(" - ") << eta.Format(_T("%b")).Mid(0, 4)
4123 << eta.Format(_T(" %d %H:%M"));
4124 } else
4125 s << _T(" ---- ----");
4126 }
4127 m_pRouteRolloverWin->SetString(s);
4128
4129 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4130 LEG_ROLLOVER, win_size);
4131 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4132 m_pRouteRolloverWin->IsActive(true);
4133 b_need_refresh = true;
4134 showRouteRollover = true;
4135 break;
4136 }
4137 } else
4138 node = node->GetNext();
4139 }
4140 } else {
4141 // Is the cursor still in select radius, and not timed out?
4142 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4143 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4144 m_pRolloverRouteSeg))
4145 showRouteRollover = false;
4146 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4147 showRouteRollover = false;
4148 else
4149 showRouteRollover = true;
4150 }
4151
4152 // If currently creating a route, do not show this rollover window
4153 if (m_routeState) showRouteRollover = false;
4154
4155 // Similar for AIS target rollover window
4156 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4157 showRouteRollover = false;
4158
4159 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4160 !showRouteRollover) {
4161 m_pRouteRolloverWin->IsActive(false);
4162 m_pRolloverRouteSeg = NULL;
4163 m_pRouteRolloverWin->Destroy();
4164 m_pRouteRolloverWin = NULL;
4165 b_need_refresh = true;
4166 } else if (m_pRouteRolloverWin && showRouteRollover) {
4167 m_pRouteRolloverWin->IsActive(true);
4168 b_need_refresh = true;
4169 }
4170
4171 // Now the Track info rollover
4172 // Show the track segment info
4173 bool showTrackRollover = false;
4174
4175 if (NULL == m_pRolloverTrackSeg) {
4176 // Get a list of all selectable sgements, and search for the first
4177 // visible segment as the rollover target.
4178
4179 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4180 SelectableItemList SelList = pSelect->FindSelectionList(
4181 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4182 wxSelectableItemListNode *node = SelList.GetFirst();
4183 while (node) {
4184 SelectItem *pFindSel = node->GetData();
4185
4186 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4187
4188 if (pt && pt->IsVisible()) {
4189 m_pRolloverTrackSeg = pFindSel;
4190 showTrackRollover = true;
4191
4192 if (NULL == m_pTrackRolloverWin) {
4193 m_pTrackRolloverWin = new RolloverWin(this, 10);
4194 m_pTrackRolloverWin->IsActive(false);
4195 }
4196
4197 if (!m_pTrackRolloverWin->IsActive()) {
4198 wxString s;
4199 TrackPoint *segShow_point_a =
4200 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4201 TrackPoint *segShow_point_b =
4202 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4203
4204 double brg, dist;
4205 DistanceBearingMercator(
4206 segShow_point_b->m_lat, segShow_point_b->m_lon,
4207 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4208
4209 if (!pt->m_bIsInLayer)
4210 s.Append(_("Track") + _T(": "));
4211 else
4212 s.Append(_("Layer Track: "));
4213
4214 if (pt->GetName().IsEmpty())
4215 s.Append(_("(unnamed)"));
4216 else
4217 s.Append(pt->GetName());
4218 double tlenght = pt->Length();
4219 s << _T("\n") << _("Total Track: ")
4220 << FormatDistanceAdaptive(tlenght);
4221 if (pt->GetLastPoint()->GetTimeString() &&
4222 pt->GetPoint(0)->GetTimeString()) {
4223 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4224 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4225 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4226 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4227 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4228 s << wxString::Format(_T(" %.1f "), (float)(tlenght / htime))
4229 << getUsrSpeedUnit();
4230 s << wxString(htime > 24. ? ttime.Format(_T(" %Dd %H:%M"))
4231 : ttime.Format(_T(" %H:%M")));
4232 }
4233 }
4234
4235 if (g_bShowTrackPointTime && strlen(segShow_point_b->GetTimeString()))
4236 s << _T("\n") << _("Segment Created: ")
4237 << segShow_point_b->GetTimeString();
4238
4239 s << _T("\n");
4240 if (g_bShowTrue)
4241 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4242 0x00B0);
4243
4244 if (g_bShowMag) {
4245 double latAverage =
4246 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4247 double lonAverage =
4248 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4249 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4250
4251 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4252 0x00B0);
4253 }
4254
4255 s << FormatDistanceAdaptive(dist);
4256
4257 if (segShow_point_a->GetTimeString() &&
4258 segShow_point_b->GetTimeString()) {
4259 wxDateTime apoint = segShow_point_a->GetCreateTime();
4260 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4261 if (apoint.IsValid() && bpoint.IsValid()) {
4262 double segmentSpeed = toUsrSpeed(
4263 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4264 s << wxString::Format(_T(" %.1f "), (float)segmentSpeed)
4265 << getUsrSpeedUnit();
4266 }
4267 }
4268
4269 m_pTrackRolloverWin->SetString(s);
4270
4271 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4272 LEG_ROLLOVER, win_size);
4273 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4274 m_pTrackRolloverWin->IsActive(true);
4275 b_need_refresh = true;
4276 showTrackRollover = true;
4277 break;
4278 }
4279 } else
4280 node = node->GetNext();
4281 }
4282 } else {
4283 // Is the cursor still in select radius, and not timed out?
4284 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4285 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4286 m_pRolloverTrackSeg))
4287 showTrackRollover = false;
4288 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4289 showTrackRollover = false;
4290 else
4291 showTrackRollover = true;
4292 }
4293
4294 // Similar for AIS target rollover window
4295 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4296 showTrackRollover = false;
4297
4298 // Similar for route rollover window
4299 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4300 showTrackRollover = false;
4301
4302 // TODO We onlt show tracks on primary canvas....
4303 // if(!IsPrimaryCanvas())
4304 // showTrackRollover = false;
4305
4306 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4307 !showTrackRollover) {
4308 m_pTrackRolloverWin->IsActive(false);
4309 m_pRolloverTrackSeg = NULL;
4310 m_pTrackRolloverWin->Destroy();
4311 m_pTrackRolloverWin = NULL;
4312 b_need_refresh = true;
4313 } else if (m_pTrackRolloverWin && showTrackRollover) {
4314 m_pTrackRolloverWin->IsActive(true);
4315 b_need_refresh = true;
4316 }
4317
4318 if (b_need_refresh) Refresh();
4319}
4320
4321void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4322 if ((GetShowENCLights() || m_bsectors_shown) &&
4323 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4324 extendedSectorLegs)) {
4325 if (!m_bsectors_shown) {
4326 ReloadVP(false);
4327 m_bsectors_shown = true;
4328 }
4329 } else {
4330 if (m_bsectors_shown) {
4331 ReloadVP(false);
4332 m_bsectors_shown = false;
4333 }
4334 }
4335
4336// This is here because GTK status window update is expensive..
4337// cairo using pango rebuilds the font every time so is very
4338// inefficient
4339// Anyway, only update the status bar when this timer expires
4340#if defined(__WXGTK__) || defined(__WXQT__)
4341 {
4342 // Check the absolute range of the cursor position
4343 // There could be a window wherein the chart geoereferencing is not
4344 // valid....
4345 double cursor_lat, cursor_lon;
4346 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4347
4348 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4349 while (cursor_lon < -180.) cursor_lon += 360.;
4350
4351 while (cursor_lon > 180.) cursor_lon -= 360.;
4352
4353 SetCursorStatus(cursor_lat, cursor_lon);
4354 }
4355 }
4356#endif
4357}
4358
4359void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4360 if (!parent_frame->m_pStatusBar) return;
4361
4362 wxString s1;
4363 s1 += _T(" ");
4364 s1 += toSDMM(1, cursor_lat);
4365 s1 += _T(" ");
4366 s1 += toSDMM(2, cursor_lon);
4367
4368 if (STAT_FIELD_CURSOR_LL >= 0)
4369 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4370
4371 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4372
4373 double brg, dist;
4374 wxString sm;
4375 wxString st;
4376 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4377 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4378 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4379
4380 wxString s = st + sm;
4381 s << FormatDistanceAdaptive(dist);
4382
4383 // CUSTOMIZATION - LIVE ETA OPTION
4384 // -------------------------------------------------------
4385 // Calculate an "live" ETA based on route starting from the current
4386 // position of the boat and goes to the cursor of the mouse.
4387 // In any case, an standard ETA will be calculated with a default speed
4388 // of the boat to give an estimation of the route (in particular if GPS
4389 // is off).
4390
4391 // Display only if option "live ETA" is selected in Settings > Display >
4392 // General.
4393 if (g_bShowLiveETA) {
4394 float realTimeETA;
4395 float boatSpeed;
4396 float boatSpeedDefault = g_defaultBoatSpeed;
4397
4398 // Calculate Estimate Time to Arrival (ETA) in minutes
4399 // Check before is value not closed to zero (it will make an very big
4400 // number...)
4401 if (!std::isnan(gSog)) {
4402 boatSpeed = gSog;
4403 if (boatSpeed < 0.5) {
4404 realTimeETA = 0;
4405 } else {
4406 realTimeETA = dist / boatSpeed * 60;
4407 }
4408 } else {
4409 realTimeETA = 0;
4410 }
4411
4412 // Add space after distance display
4413 s << " ";
4414 // Display ETA
4415 s << minutesToHoursDays(realTimeETA);
4416
4417 // In any case, display also an ETA with default speed at 6knts
4418
4419 s << " [@";
4420 s << wxString::Format(_T("%d"), (int)toUsrSpeed(boatSpeedDefault, -1));
4421 s << wxString::Format(_T("%s"), getUsrSpeedUnit(-1));
4422 s << " ";
4423 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4424 s << "]";
4425 }
4426 // END OF - LIVE ETA OPTION
4427
4428 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4429}
4430
4431// CUSTOMIZATION - FORMAT MINUTES
4432// -------------------------------------------------------
4433// New function to format minutes into a more readable format:
4434// * Hours + minutes, or
4435// * Days + hours.
4436wxString minutesToHoursDays(float timeInMinutes) {
4437 wxString s;
4438
4439 if (timeInMinutes == 0) {
4440 s << "--min";
4441 }
4442
4443 // Less than 60min, keep time in minutes
4444 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4445 s << wxString::Format(_T("%d"), (int)timeInMinutes);
4446 s << "min";
4447 }
4448
4449 // Between 1h and less than 24h, display time in hours, minutes
4450 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4451 int hours;
4452 int min;
4453 hours = (int)timeInMinutes / 60;
4454 min = (int)timeInMinutes % 60;
4455
4456 if (min == 0) {
4457 s << wxString::Format(_T("%d"), hours);
4458 s << "h";
4459 } else {
4460 s << wxString::Format(_T("%d"), hours);
4461 s << "h";
4462 s << wxString::Format(_T("%d"), min);
4463 s << "min";
4464 }
4465
4466 }
4467
4468 // More than 24h, display time in days, hours
4469 else if (timeInMinutes > 24 * 60) {
4470 int days;
4471 int hours;
4472 days = (int)(timeInMinutes / 60) / 24;
4473 hours = (int)(timeInMinutes / 60) % 24;
4474
4475 if (hours == 0) {
4476 s << wxString::Format(_T("%d"), days);
4477 s << "d";
4478 } else {
4479 s << wxString::Format(_T("%d"), days);
4480 s << "d";
4481 s << wxString::Format(_T("%d"), hours);
4482 s << "h";
4483 }
4484 }
4485
4486 return s;
4487}
4488
4489// END OF CUSTOMIZATION - FORMAT MINUTES
4490// Thanks open source code ;-)
4491// -------------------------------------------------------
4492
4493void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4494 double clat, clon;
4495 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4496 *lat = clat;
4497 *lon = clon;
4498}
4499
4500void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4501 wxPoint2DDouble *r) {
4502 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4503}
4504
4506 double rlon, wxPoint2DDouble *r) {
4507 // If the Current Chart is a raster chart, and the
4508 // requested lat/long is within the boundaries of the chart,
4509 // and the VP is not rotated,
4510 // then use the embedded BSB chart georeferencing algorithm
4511 // for greater accuracy
4512 // Additionally, use chart embedded georef if the projection is TMERC
4513 // i.e. NOT MERCATOR and NOT POLYCONIC
4514
4515 // If for some reason the chart rejects the request by returning an error,
4516 // then fall back to Viewport Projection estimate from canvas parameters
4517 if (!g_bopengl && m_singleChart &&
4518 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4519 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4520 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4521 (m_singleChart->GetChartProjectionType() !=
4522 PROJECTION_TRANSVERSE_MERCATOR) &&
4523 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4524 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4525 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4526 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4527 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4528 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4529 // Cur_BSB_Ch->GetCOVRTablenPoints
4530 // ( 0 ), rlon,
4531 // rlat );
4532 // bInside = true;
4533 // if ( bInside )
4534 if (Cur_BSB_Ch) {
4535 // This is a Raster chart....
4536 // If the VP is changing, the raster chart parameters may not yet be
4537 // setup So do that before accessing the chart's embedded
4538 // georeferencing
4539 Cur_BSB_Ch->SetVPRasterParms(vp);
4540 double rpixxd, rpixyd;
4541 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4542 r->m_x = rpixxd;
4543 r->m_y = rpixyd;
4544 return;
4545 }
4546 }
4547 }
4548
4549 // if needed, use the VPoint scaling estimator,
4550 *r = vp.GetDoublePixFromLL(rlat, rlon);
4551}
4552
4553// This routine might be deleted and all of the rendering improved
4554// to have floating point accuracy
4555bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4556 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4557}
4558
4559bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4560 wxPoint *r) {
4561 wxPoint2DDouble p;
4562 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4563
4564 // some projections give nan values when invisible values (other side of
4565 // world) are requested we should stop using integer coordinates or return
4566 // false here (and test it everywhere)
4567 if (std::isnan(p.m_x)) {
4568 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4569 return false;
4570 }
4571
4572 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4573 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4574 else
4575 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4576
4577 return true;
4578}
4579
4580void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4581 double &lon) {
4582 // If the Current Chart is a raster chart, and the
4583 // requested x,y is within the boundaries of the chart,
4584 // and the VP is not rotated,
4585 // then use the embedded BSB chart georeferencing algorithm
4586 // for greater accuracy
4587 // Additionally, use chart embedded georef if the projection is TMERC
4588 // i.e. NOT MERCATOR and NOT POLYCONIC
4589
4590 // If for some reason the chart rejects the request by returning an error,
4591 // then fall back to Viewport Projection estimate from canvas parameters
4592 bool bUseVP = true;
4593
4594 if (!g_bopengl && m_singleChart &&
4595 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4596 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4597 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4598 (m_singleChart->GetChartProjectionType() !=
4599 PROJECTION_TRANSVERSE_MERCATOR) &&
4600 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4601 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4602 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4603 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4604
4605 // TODO maybe need iterative process to validate bInside
4606 // first pass is mercator, then check chart boundaries
4607
4608 if (Cur_BSB_Ch) {
4609 // This is a Raster chart....
4610 // If the VP is changing, the raster chart parameters may not yet be
4611 // setup So do that before accessing the chart's embedded
4612 // georeferencing
4613 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4614
4615 double slat, slon;
4616 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4617 lat = slat;
4618
4619 if (slon < -180.)
4620 slon += 360.;
4621 else if (slon > 180.)
4622 slon -= 360.;
4623
4624 lon = slon;
4625 bUseVP = false;
4626 }
4627 }
4628 }
4629
4630 // if needed, use the VPoint scaling estimator
4631 if (bUseVP) {
4632 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4633 }
4634}
4635
4637 StopMovement();
4638 DoZoomCanvas(factor, false);
4639 extendedSectorLegs.clear();
4640}
4641
4642void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4643 bool stoptimer) {
4644 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4645
4646 if (g_bsmoothpanzoom) {
4647 if (StartTimedMovement(stoptimer)) {
4648 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4649 m_zoom_factor = factor;
4650 }
4651
4652 m_zoom_target = VPoint.chart_scale / factor;
4653 } else {
4654 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4655
4656 DoZoomCanvas(factor, can_zoom_to_cursor);
4657 }
4658
4659 extendedSectorLegs.clear();
4660}
4661
4662void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4663 // possible on startup
4664 if (!ChartData) return;
4665 if (!m_pCurrentStack) return;
4666
4667 /* TODO: queue the quilted loading code to a background thread
4668 so yield is never called from here, and also rendering is not delayed */
4669
4670 // Cannot allow Yield() re-entrancy here
4671 if (m_bzooming) return;
4672 m_bzooming = true;
4673
4674 double old_ppm = GetVP().view_scale_ppm;
4675
4676 // Capture current cursor position for zoom to cursor
4677 double zlat = m_cursor_lat;
4678 double zlon = m_cursor_lon;
4679
4680 double proposed_scale_onscreen =
4681 GetVP().chart_scale /
4682 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4683 bool b_do_zoom = false;
4684
4685 if (factor > 1) {
4686 b_do_zoom = true;
4687
4688 // double zoom_factor = factor;
4689
4690 ChartBase *pc = NULL;
4691
4692 if (!VPoint.b_quilt) {
4693 pc = m_singleChart;
4694 } else {
4695 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4696 if (new_db_index >= 0)
4697 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4698 else { // for whatever reason, no reference chart is known
4699 // Choose the smallest scale chart on the current stack
4700 // and then adjust for scale range
4701 int current_ref_stack_index = -1;
4702 if (m_pCurrentStack->nEntry) {
4703 int trial_index =
4704 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4705 m_pQuilt->SetReferenceChart(trial_index);
4706 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4707 if (new_db_index >= 0)
4708 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4709 }
4710 }
4711
4712 if (m_pCurrentStack)
4713 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4714 new_db_index); // highlite the correct bar entry
4715 }
4716
4717 if (pc) {
4718 // double target_scale_ppm = GetVPScale() * zoom_factor;
4719 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4720 // target_scale_ppm;
4721
4722 // Query the chart to determine the appropriate zoom range
4723 double min_allowed_scale =
4724 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4725
4726 if (proposed_scale_onscreen < min_allowed_scale) {
4727 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4728 m_zoom_factor = 1; /* stop zooming */
4729 b_do_zoom = false;
4730 } else
4731 proposed_scale_onscreen = min_allowed_scale;
4732 }
4733
4734 } else {
4735 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4736 }
4737
4738 } else if (factor < 1) {
4739 b_do_zoom = true;
4740
4741 ChartBase *pc = NULL;
4742
4743 bool b_smallest = false;
4744
4745 if (!VPoint.b_quilt) { // not quilted
4746 pc = m_singleChart;
4747
4748 if (pc) {
4749 // If m_singleChart is not on the screen, unbound the zoomout
4750 LLBBox viewbox = VPoint.GetBBox();
4751 // BoundingBox chart_box;
4752 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4753 double max_allowed_scale;
4754
4755 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4756
4757 // We can allow essentially unbounded zoomout in single chart mode
4758 // if( ChartData->GetDBBoundingBox( current_index,
4759 // &chart_box ) &&
4760 // !viewbox.IntersectOut( chart_box ) )
4761 // // Clamp the minimum scale zoom-out to the value
4762 // specified by the chart max_allowed_scale =
4763 // wxMin(max_allowed_scale, 4.0 *
4764 // pc->GetNormalScaleMax(
4765 // GetCanvasScaleFactor(),
4766 // GetCanvasWidth() ) );
4767 if (proposed_scale_onscreen > max_allowed_scale) {
4768 m_zoom_factor = 1; /* stop zooming */
4769 proposed_scale_onscreen = max_allowed_scale;
4770 }
4771 }
4772
4773 } else {
4774 int new_db_index = m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4775 if (new_db_index >= 0)
4776 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4777
4778 if (m_pCurrentStack)
4779 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4780 new_db_index); // highlite the correct bar entry
4781
4782 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4783
4784 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4785 proposed_scale_onscreen =
4786 wxMin(proposed_scale_onscreen,
4787 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4788 }
4789
4790 // set a minimum scale
4791 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4792 m_absolute_min_scale_ppm)
4793 b_do_zoom = false;
4794 }
4795
4796 double new_scale =
4797 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4798
4799 if (b_do_zoom) {
4800 // Disable ZTC if lookahead is ON, and currently b_follow is active
4801 bool b_allow_ztc = true;
4802 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4803 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4804 if (m_bLookAhead) {
4805 double brg, distance;
4806 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4807 &distance);
4808 dir_to_shift = brg;
4809 meters_to_shift = distance * 1852;
4810 }
4811 // Arrange to combine the zoom and pan into one operation for smoother
4812 // appearance
4813 SetVPScale(new_scale, false); // adjust, but deferred refresh
4814 wxPoint r;
4815 GetCanvasPointPix(zlat, zlon, &r);
4816 // this will emit the Refresh()
4817 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4818 } else {
4819 SetVPScale(new_scale);
4820 if (m_bFollow) DoCanvasUpdate();
4821 }
4822 }
4823
4824 m_bzooming = false;
4825}
4826int rot;
4827void ChartCanvas::RotateCanvas(double dir) {
4828 // SetUpMode(NORTH_UP_MODE);
4829
4830 if (g_bsmoothpanzoom) {
4831 if (StartTimedMovement()) {
4832 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4833 m_rotation_speed = dir * 60;
4834 }
4835 } else {
4836 double speed = dir * 10;
4837 if (m_modkeys == wxMOD_ALT) speed /= 20;
4838 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4839 }
4840}
4841
4842void ChartCanvas::DoRotateCanvas(double rotation) {
4843 while (rotation < 0) rotation += 2 * PI;
4844 while (rotation > 2 * PI) rotation -= 2 * PI;
4845
4846 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4847
4848 SetVPRotation(rotation);
4849 parent_frame->UpdateRotationState(VPoint.rotation);
4850}
4851
4852void ChartCanvas::DoTiltCanvas(double tilt) {
4853 while (tilt < 0) tilt = 0;
4854 while (tilt > .95) tilt = .95;
4855
4856 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4857
4858 VPoint.tilt = tilt;
4859 Refresh(false);
4860}
4861
4862void ChartCanvas::TogglebFollow(void) {
4863 if (!m_bFollow)
4864 SetbFollow();
4865 else
4866 ClearbFollow();
4867}
4868
4869void ChartCanvas::ClearbFollow(void) {
4870 m_bFollow = false; // update the follow flag
4871
4872 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4873
4874 UpdateFollowButtonState();
4875
4876 DoCanvasUpdate();
4877 ReloadVP();
4878 parent_frame->SetChartUpdatePeriod();
4879}
4880
4881void ChartCanvas::SetbFollow(void) {
4882 // Is the OWNSHIP on-screen?
4883 // If not, then reset the OWNSHIP offset to 0 (center screen)
4884 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4885 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4886 m_OSoffsetx = 0;
4887 m_OSoffsety = 0;
4888 }
4889
4890 // Apply the present b_follow offset values to ship position
4891 wxPoint2DDouble p;
4892 GetDoubleCanvasPointPix(gLat, gLon, &p);
4893 p.m_x += m_OSoffsetx;
4894 p.m_y -= m_OSoffsety;
4895
4896 // compute the target center screen lat/lon
4897 double dlat, dlon;
4898 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4899
4900 JumpToPosition(dlat, dlon, GetVPScale());
4901 m_bFollow = true;
4902
4903 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4904 UpdateFollowButtonState();
4905
4906 if (!g_bSmoothRecenter) {
4907 DoCanvasUpdate();
4908 ReloadVP();
4909 }
4910 parent_frame->SetChartUpdatePeriod();
4911}
4912
4913void ChartCanvas::UpdateFollowButtonState(void) {
4914 if (m_muiBar) {
4915 if (!m_bFollow)
4916 m_muiBar->SetFollowButtonState(0);
4917 else {
4918 if (m_bLookAhead)
4919 m_muiBar->SetFollowButtonState(2);
4920 else
4921 m_muiBar->SetFollowButtonState(1);
4922 }
4923 }
4924
4925#ifdef __ANDROID__
4926 if (!m_bFollow)
4927 androidSetFollowTool(0);
4928 else {
4929 if (m_bLookAhead)
4930 androidSetFollowTool(2);
4931 else
4932 androidSetFollowTool(1);
4933 }
4934#endif
4935}
4936
4937void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4938 if (g_bSmoothRecenter && !m_routeState) {
4939 if (StartSmoothJump(lat, lon, scale_ppm))
4940 return;
4941 else {
4942 // move closer to the target destination, and try again
4943 double gcDist, gcBearingEnd;
4944 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4945 &gcBearingEnd);
4946 gcBearingEnd += 180;
4947 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4948 GetCanvasWidth() / GetVPScale(); // meters
4949 double lon_offset =
4950 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4951 double new_lat = lat + (lat_offset / (1852 * 60));
4952 double new_lon = lon + (lon_offset / (1852 * 60));
4953 SetViewPoint(new_lat, new_lon);
4954 ReloadVP();
4955 StartSmoothJump(lat, lon, scale_ppm);
4956 return;
4957 }
4958 }
4959
4960 if (lon > 180.0) lon -= 360.0;
4961 m_vLat = lat;
4962 m_vLon = lon;
4963 StopMovement();
4964 m_bFollow = false;
4965
4966 if (!GetQuiltMode()) {
4967 double skew = 0;
4968 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4969 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4970 } else {
4971 if (scale_ppm != GetVPScale()) {
4972 // XXX should be done in SetViewPoint
4973 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4974 AdjustQuiltRefChart();
4975 }
4976 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4977 }
4978
4979 ReloadVP();
4980
4981 UpdateFollowButtonState();
4982
4983 // TODO
4984 // if( g_pi_manager ) {
4985 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4986 // }
4987}
4988
4989bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
4990 // Check distance to jump, in pixels at current chart scale
4991 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
4992 // width.
4993 double gcDist;
4994 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
4995 double distance_pixels = gcDist * GetVPScale();
4996 if (distance_pixels > 0.5 * GetCanvasWidth()) {
4997 // Jump is too far, try again
4998 return false;
4999 }
5000
5001 // Save where we're coming from
5002 m_startLat = m_vLat;
5003 m_startLon = m_vLon;
5004 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5005
5006 // Save where we want to end up
5007 m_endLat = lat;
5008 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5009 m_endScale = scale_ppm;
5010
5011 // Setup timing
5012 m_animationDuration = 600; // ms
5013 m_animationStart = wxGetLocalTimeMillis();
5014
5015 // Stop any previous movement, ensure no conflicts
5016 StopMovement();
5017 m_bFollow = false;
5018
5019 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5020 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5021 m_animationActive = true;
5022
5023 return true;
5024}
5025
5026void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5027 // Calculate time fraction from 0..1
5028 wxLongLong now = wxGetLocalTimeMillis();
5029 double elapsed = (now - m_animationStart).ToDouble();
5030 double t = elapsed / m_animationDuration.ToDouble();
5031 if (t > 1.0) t = 1.0;
5032
5033 // Ease function for smoother movement
5034 double e = easeOutCubic(t);
5035
5036 // Interpolate lat/lon/scale
5037 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5038 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5039 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5040
5041 // Update viewpoint
5042 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5043 // portion)
5044 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5045 ReloadVP();
5046
5047 // If we reached the end, stop the timer and finalize
5048 if (t >= 1.0) {
5049 m_easeTimer.Stop();
5050 m_animationActive = false;
5051 UpdateFollowButtonState();
5052 ZoomCanvasSimple(1.0001);
5053 DoCanvasUpdate();
5054 ReloadVP();
5055 }
5056}
5057
5058bool ChartCanvas::PanCanvas(double dx, double dy) {
5059 if (!ChartData) return false;
5060
5061 if (g_btouch) {
5062 // Stop bfollow state, without a refresh
5063 m_bFollow = false; // update the follow flag
5064 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
5065 UpdateFollowButtonState();
5066 // Clear the bfollow offset
5067 m_OSoffsetx = 0;
5068 m_OSoffsety = 0;
5069 }
5070
5071 extendedSectorLegs.clear();
5072
5073 // double clat = VPoint.clat, clon = VPoint.clon;
5074 double dlat, dlon;
5075 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5076
5077 int iters = 0;
5078 for (;;) {
5079 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5080
5081 if (iters++ > 5) return false;
5082 if (!std::isnan(dlat)) break;
5083
5084 dx *= .5, dy *= .5;
5085 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5086 }
5087
5088 // avoid overshooting the poles
5089 if (dlat > 90)
5090 dlat = 90;
5091 else if (dlat < -90)
5092 dlat = -90;
5093
5094 if (dlon > 360.) dlon -= 360.;
5095 if (dlon < -360.) dlon += 360.;
5096
5097 // This should not really be necessary, but round-trip georef on some
5098 // charts is not perfect, So we can get creep on repeated unidimensional
5099 // pans, and corrupt chart cacheing.......
5100
5101 // But this only works on north-up projections
5102 // TODO: can we remove this now?
5103 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5104 // .001 ) ) {
5105 //
5106 // if( dx == 0 ) dlon = clon;
5107 // if( dy == 0 ) dlat = clat;
5108 // }
5109
5110 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5111
5112 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5113
5114 if (VPoint.b_quilt) {
5115 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5116 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5117 // Tweak the scale slightly for a new ref chart
5118 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5119 if (pc) {
5120 double tweak_scale_ppm =
5121 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5122 SetVPScale(tweak_scale_ppm);
5123 }
5124 }
5125
5126 if (new_ref_dbIndex == -1) {
5127#pragma GCC diagnostic push
5128#pragma GCC diagnostic ignored "-Warray-bounds"
5129 // The compiler sees a -1 index being used. Does not happen, though.
5130
5131 // for whatever reason, no reference chart is known
5132 // Probably panned out of the coverage region
5133 // If any charts are anywhere on-screen, choose the smallest
5134 // scale chart on the screen to be a new reference chart.
5135 int trial_index = -1;
5136 if (m_pCurrentStack->nEntry) {
5137 int trial_index =
5138 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5139 }
5140
5141 if (trial_index < 0) {
5142 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5143 if (full_screen_array.size())
5144 trial_index = full_screen_array[full_screen_array.size() - 1];
5145 }
5146
5147 if (trial_index >= 0) {
5148 m_pQuilt->SetReferenceChart(trial_index);
5149 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5150 VPoint.rotation);
5151 ReloadVP();
5152 }
5153#pragma GCC diagnostic pop
5154 }
5155 }
5156
5157 // Turn off bFollow only if the ownship has left the screen
5158 if (m_bFollow) {
5159 double offx, offy;
5160 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5161
5162 double offset_angle = atan2(offy, offx);
5163 double offset_distance = sqrt((offy * offy) + (offx * offx));
5164 double chart_angle = GetVPRotation();
5165 double target_angle = chart_angle - offset_angle;
5166 double d_east_mod = offset_distance * cos(target_angle);
5167 double d_north_mod = offset_distance * sin(target_angle);
5168
5169 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5170 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5171
5172 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5173 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5174 m_bFollow = false; // update the follow flag
5175 UpdateFollowButtonState();
5176 }
5177 }
5178
5179 Refresh(false);
5180
5181 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5182
5183 return true;
5184}
5185
5186void ChartCanvas::ReloadVP(bool b_adjust) {
5187 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5188
5189 LoadVP(VPoint, b_adjust);
5190}
5191
5192void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5193#ifdef ocpnUSE_GL
5194 if (g_bopengl && m_glcc) {
5195 m_glcc->Invalidate();
5196 if (m_glcc->GetSize() != GetSize()) {
5197 m_glcc->SetSize(GetSize());
5198 }
5199 } else
5200#endif
5201 {
5202 m_cache_vp.Invalidate();
5203 m_bm_cache_vp.Invalidate();
5204 }
5205
5206 VPoint.Invalidate();
5207
5208 if (m_pQuilt) m_pQuilt->Invalidate();
5209
5210 // Make sure that the Selected Group is sensible...
5211 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5212 // m_groupIndex = 0;
5213 // if( !CheckGroup( m_groupIndex ) )
5214 // m_groupIndex = 0;
5215
5216 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5217 vp.m_projection_type, b_adjust);
5218}
5219
5220void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5221 m_pQuilt->SetReferenceChart(dbIndex);
5222 VPoint.Invalidate();
5223 m_pQuilt->Invalidate();
5224}
5225
5226double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5227 if (m_pQuilt)
5228 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5229 else
5230 return vp.view_scale_ppm;
5231}
5232
5233// Verify and adjust the current reference chart,
5234// so that it will not lead to excessive overzoom or underzoom onscreen
5235int ChartCanvas::AdjustQuiltRefChart() {
5236 int ret = -1;
5237 if (m_pQuilt) {
5238 wxASSERT(ChartData);
5239 ChartBase *pc =
5240 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5241 if (pc) {
5242 double min_ref_scale =
5243 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5244 double max_ref_scale =
5245 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5246
5247 if (VPoint.chart_scale < min_ref_scale) {
5248 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5249 } else if (VPoint.chart_scale > max_ref_scale) {
5250 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5251 } else {
5252 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5253
5254 int ref_family = pc->GetChartFamily();
5255
5256 if (!brender_ok) {
5257 unsigned int target_stack_index = 0;
5258 int target_stack_index_check =
5259 m_pQuilt->GetExtendedStackIndexArray()
5260 [m_pQuilt->GetRefChartdbIndex()]; // Lookup
5261
5262 if (wxNOT_FOUND != target_stack_index_check)
5263 target_stack_index = target_stack_index_check;
5264
5265 int extended_array_count =
5266 m_pQuilt->GetExtendedStackIndexArray().size();
5267 while ((!brender_ok) &&
5268 ((int)target_stack_index < (extended_array_count - 1))) {
5269 target_stack_index++;
5270 int test_db_index =
5271 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5272
5273 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5274 IsChartQuiltableRef(test_db_index)) {
5275 // open the target, and check the min_scale
5276 ChartBase *ptest_chart =
5277 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5278 if (ptest_chart) {
5279 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5280 }
5281 }
5282 }
5283
5284 if (brender_ok) { // found a better reference chart
5285 int new_db_index =
5286 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5287 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5288 IsChartQuiltableRef(new_db_index)) {
5289 m_pQuilt->SetReferenceChart(new_db_index);
5290 ret = new_db_index;
5291 } else
5292 ret = m_pQuilt->GetRefChartdbIndex();
5293 } else
5294 ret = m_pQuilt->GetRefChartdbIndex();
5295
5296 } else
5297 ret = m_pQuilt->GetRefChartdbIndex();
5298 }
5299 } else
5300 ret = -1;
5301 }
5302
5303 return ret;
5304}
5305
5306void ChartCanvas::UpdateCanvasOnGroupChange(void) {
5307 delete m_pCurrentStack;
5308 m_pCurrentStack = new ChartStack;
5309 wxASSERT(ChartData);
5310 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5311 m_groupIndex);
5312
5313 if (m_pQuilt) {
5314 m_pQuilt->Compose(VPoint);
5315 SetFocus();
5316 }
5317}
5318
5319bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5320 double latNE, double lonNE) {
5321 // Center Point
5322 double latc = (latSW + latNE) / 2.0;
5323 double lonc = (lonSW + lonNE) / 2.0;
5324
5325 // Get scale in ppm (latitude)
5326 double ne_easting, ne_northing;
5327 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5328
5329 double sw_easting, sw_northing;
5330 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5331
5332 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5333
5334 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5335}
5336
5337bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5338 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5339 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5340}
5341
5342bool ChartCanvas::SetVPProjection(int projection) {
5343 if (!g_bopengl) // alternative projections require opengl
5344 return false;
5345
5346 // the view scale varies depending on geographic location and projection
5347 // rescale to keep the relative scale on the screen the same
5348 double prev_true_scale_ppm = m_true_scale_ppm;
5349 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5350 VPoint.skew, VPoint.rotation, projection) &&
5351 SetVPScale(wxMax(
5352 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5353 m_absolute_min_scale_ppm));
5354}
5355
5356bool ChartCanvas::SetViewPoint(double lat, double lon) {
5357 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5358 VPoint.rotation);
5359}
5360
5361bool ChartCanvas::SetVPRotation(double angle) {
5362 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5363 VPoint.skew, angle);
5364}
5365bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5366 double skew, double rotation, int projection,
5367 bool b_adjust, bool b_refresh) {
5368 bool b_ret = false;
5369 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5370 skew -= 2 * PI;
5371 // Any sensible change?
5372 if (VPoint.IsValid()) {
5373 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5374 (fabs(VPoint.skew - skew) < 1e-9) &&
5375 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5376 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5377 (VPoint.m_projection_type == projection ||
5378 projection == PROJECTION_UNKNOWN))
5379 return false;
5380 }
5381 if (VPoint.m_projection_type != projection)
5382 VPoint.InvalidateTransformCache(); // invalidate
5383
5384 // Take a local copy of the last viewport
5385 ViewPort last_vp = VPoint;
5386
5387 VPoint.skew = skew;
5388 VPoint.clat = lat;
5389 VPoint.clon = lon;
5390 VPoint.rotation = rotation;
5391 VPoint.view_scale_ppm = scale_ppm;
5392 if (projection != PROJECTION_UNKNOWN)
5393 VPoint.SetProjectionType(projection);
5394 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5395 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5396
5397 // don't allow latitude above 88 for mercator (90 is infinity)
5398 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5399 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5400 if (VPoint.clat > 89.5)
5401 VPoint.clat = 89.5;
5402 else if (VPoint.clat < -89.5)
5403 VPoint.clat = -89.5;
5404 }
5405
5406 // don't zoom out too far for transverse mercator polyconic until we resolve
5407 // issues
5408 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5409 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5410 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5411
5412 // SetVPRotation(rotation);
5413
5414 if (!g_bopengl) // tilt is not possible without opengl
5415 VPoint.tilt = 0;
5416
5417 if ((VPoint.pix_width <= 0) ||
5418 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5419 return false;
5420
5421 bool bwasValid = VPoint.IsValid();
5422 VPoint.Validate(); // Mark this ViewPoint as OK
5423
5424 // Has the Viewport scale changed? If so, invalidate the vp
5425 if (last_vp.view_scale_ppm != scale_ppm) {
5426 m_cache_vp.Invalidate();
5427 InvalidateGL();
5428 }
5429
5430 // A preliminary value, may be tweaked below
5431 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5432
5433 // recompute cursor position
5434 // and send to interested plugins if the mouse is actually in this window
5435 int mouseX = mouse_x;
5436 int mouseY = mouse_y;
5437 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5438 (mouseY < VPoint.pix_height)) {
5439 double lat, lon;
5440 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5441 m_cursor_lat = lat;
5442 m_cursor_lon = lon;
5443 SendCursorLatLonToAllPlugIns(lat, lon);
5444 }
5445
5446 if (!VPoint.b_quilt && m_singleChart) {
5447 VPoint.SetBoxes();
5448
5449 // Allow the chart to adjust the new ViewPort for performance optimization
5450 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5451 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5452
5453 // If there is a sensible change in the chart render, refresh the whole
5454 // screen
5455 if ((!m_cache_vp.IsValid()) ||
5456 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5457 Refresh(false);
5458 b_ret = true;
5459 } else {
5460 wxPoint cp_last, cp_this;
5461 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5462 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5463
5464 if (cp_last != cp_this) {
5465 Refresh(false);
5466 b_ret = true;
5467 }
5468 }
5469 // Create the stack
5470 if (m_pCurrentStack) {
5471 assert(ChartData != 0);
5472 int current_db_index;
5473 current_db_index =
5474 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5475
5476 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5477 m_groupIndex);
5478 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5479 }
5480
5481 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5482 }
5483
5484 // Handle the quilted case
5485 if (VPoint.b_quilt) {
5486 if (last_vp.view_scale_ppm != scale_ppm)
5487 m_pQuilt->InvalidateAllQuiltPatchs();
5488
5489 // Create the quilt
5490 if (ChartData /*&& ChartData->IsValid()*/) {
5491 if (!m_pCurrentStack) return false;
5492
5493 int current_db_index;
5494 current_db_index =
5495 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5496
5497 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5498 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5499
5500 // Check to see if the current quilt reference chart is in the new stack
5501 int current_ref_stack_index = -1;
5502 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5503 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5504 current_ref_stack_index = i;
5505 }
5506
5507 if (g_bFullScreenQuilt) {
5508 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5509 }
5510
5511 // We might need a new Reference Chart
5512 bool b_needNewRef = false;
5513
5514 // If the new stack does not contain the current ref chart....
5515 if ((-1 == current_ref_stack_index) &&
5516 (m_pQuilt->GetRefChartdbIndex() >= 0))
5517 b_needNewRef = true;
5518
5519 // Would the current Ref Chart be excessively underzoomed?
5520 // We need to check this here to be sure, since we cannot know where the
5521 // reference chart was assigned. For instance, the reference chart may
5522 // have been selected from the config file, or from a long jump with a
5523 // chart family switch implicit. Anyway, we check to be sure....
5524 bool renderable = true;
5525 ChartBase *referenceChart =
5526 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5527 if (referenceChart) {
5528 double chartMaxScale = referenceChart->GetNormalScaleMax(
5529 GetCanvasScaleFactor(), GetCanvasWidth());
5530 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5531 }
5532 if (!renderable) b_needNewRef = true;
5533
5534 // Need new refchart?
5535 if (b_needNewRef) {
5536 const ChartTableEntry &cte_ref =
5537 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5538 int target_scale = cte_ref.GetScale();
5539 int target_type = cte_ref.GetChartType();
5540 int candidate_stack_index;
5541
5542 // reset the ref chart in a way that does not lead to excessive
5543 // underzoom, for performance reasons Try to find a chart that is the
5544 // same type, and has a scale of just smaller than the current ref
5545 // chart
5546
5547 candidate_stack_index = 0;
5548 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5549 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5550 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5551 int candidate_scale = cte_candidate.GetScale();
5552 int candidate_type = cte_candidate.GetChartType();
5553
5554 if ((candidate_scale >= target_scale) &&
5555 (candidate_type == target_type)) {
5556 bool renderable = true;
5557 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5558 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5559 if (tentative_referenceChart) {
5560 double chartMaxScale =
5561 tentative_referenceChart->GetNormalScaleMax(
5562 GetCanvasScaleFactor(), GetCanvasWidth());
5563 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5564 }
5565
5566 if (renderable) break;
5567 }
5568
5569 candidate_stack_index++;
5570 }
5571
5572 // If that did not work, look for a chart of just larger scale and
5573 // same type
5574 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5575 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5576 while (candidate_stack_index >= 0) {
5577 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5578 if (idx >= 0) {
5579 const ChartTableEntry &cte_candidate =
5580 ChartData->GetChartTableEntry(idx);
5581 int candidate_scale = cte_candidate.GetScale();
5582 int candidate_type = cte_candidate.GetChartType();
5583
5584 if ((candidate_scale <= target_scale) &&
5585 (candidate_type == target_type))
5586 break;
5587 }
5588 candidate_stack_index--;
5589 }
5590 }
5591
5592 // and if that did not work, chose stack entry 0
5593 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5594 (candidate_stack_index < 0))
5595 candidate_stack_index = 0;
5596
5597 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5598
5599 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5600 }
5601
5602 if (!g_bopengl) {
5603 // Preset the VPoint projection type to match what the quilt projection
5604 // type will be
5605 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5606
5607 // Always keep the default Mercator projection if the reference chart is
5608 // not in the PatchList or the scale is too small for it to render.
5609
5610 bool renderable = true;
5611 ChartBase *referenceChart =
5612 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5613 if (referenceChart) {
5614 double chartMaxScale = referenceChart->GetNormalScaleMax(
5615 GetCanvasScaleFactor(), GetCanvasWidth());
5616 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5617 proj = ChartData->GetDBChartProj(ref_db_index);
5618 } else
5619 proj = PROJECTION_MERCATOR;
5620
5621 VPoint.b_MercatorProjectionOverride =
5622 (m_pQuilt->GetnCharts() == 0 || !renderable);
5623
5624 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5625
5626 VPoint.SetProjectionType(proj);
5627 }
5628
5629 VPoint.SetBoxes();
5630
5631 // If this quilt will be a perceptible delta from the existing quilt,
5632 // then refresh the entire screen
5633 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5634 // Allow the quilt to adjust the new ViewPort for performance
5635 // optimization This will normally be only a fractional (i.e.
5636 // sub-pixel) adjustment...
5637 if (b_adjust) {
5638 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5639 }
5640
5641 // ChartData->ClearCacheInUseFlags();
5642 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5643
5644 // wxStopWatch sw;
5645
5646#ifdef __ANDROID__
5647 // This is an optimization for panning on touch screen systems.
5648 // The quilt composition is deferred until the OnPaint() message gets
5649 // finally removed and processed from the message queue.
5650 // Takes advantage of the fact that touch-screen pan gestures are
5651 // usually short in distance,
5652 // so not requiring a full quilt rebuild until the pan gesture is
5653 // complete.
5654 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5655 // qDebug() << "Force compose";
5656 m_pQuilt->Compose(VPoint);
5657 } else {
5658 m_pQuilt->Invalidate();
5659 }
5660#else
5661 m_pQuilt->Compose(VPoint);
5662#endif
5663
5664 // printf("comp time %ld\n", sw.Time());
5665
5666 // If the extended chart stack has changed, invalidate any cached
5667 // render bitmap
5668 // if(m_pQuilt->GetXStackHash() != hash1) {
5669 // m_bm_cache_vp.Invalidate();
5670 // InvalidateGL();
5671 // }
5672
5673 ChartData->PurgeCacheUnusedCharts(0.7);
5674
5675 if (b_refresh) Refresh(false);
5676
5677 b_ret = true;
5678 }
5679 }
5680
5681 VPoint.skew = 0.; // Quilting supports 0 Skew
5682 } else if (!g_bopengl) {
5683 OcpnProjType projection = PROJECTION_UNKNOWN;
5684 if (m_singleChart) // viewport projection must match chart projection
5685 // without opengl
5686 projection = m_singleChart->GetChartProjectionType();
5687 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5688 VPoint.SetProjectionType(projection);
5689 }
5690
5691 // Has the Viewport projection changed? If so, invalidate the vp
5692 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5693 m_cache_vp.Invalidate();
5694 InvalidateGL();
5695 }
5696
5697 UpdateCanvasControlBar(); // Refresh the Piano
5698
5699 VPoint.chart_scale = 1.0; // fallback default value
5700
5701 /*if (!VPoint.GetBBox().GetValid())*/ VPoint.SetBoxes();
5702
5703 if (VPoint.GetBBox().GetValid()) {
5704 // Update the viewpoint reference scale
5705 if (m_singleChart)
5706 VPoint.ref_scale = m_singleChart->GetNativeScale();
5707 else {
5708#ifdef __ANDROID__
5709 // This is an optimization for panning on touch screen systems.
5710 // See above.
5711 // Quilt might not be fully composed at this point, so for cm93
5712 // the reference scale may not be known.
5713 // In this case, do not update the VP ref_scale.
5714 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5715 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5716 }
5717#else
5718 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5719#endif
5720 }
5721
5722 // Calculate the on-screen displayed actual scale
5723 // by a simple traverse northward from the center point
5724 // of roughly one eighth of the canvas height
5725 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5726
5727 double delta_check =
5728 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5729 delta_check /= 8.;
5730
5731 double check_point = wxMin(89., VPoint.clat);
5732
5733 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5734
5735 double rhumbDist;
5736 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5737 VPoint.clon, 0, &rhumbDist);
5738
5739 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5740 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5741 // Calculate the distance between r1 and r in physical pixels.
5742 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5743 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5744
5745 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5746
5747 // A fall back in case of very high zoom-out, giving delta_y == 0
5748 // which can probably only happen with vector charts
5749 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5750
5751 // Another fallback, for highly zoomed out charts
5752 // This adjustment makes the displayed TrueScale correspond to the
5753 // same algorithm used to calculate the chart zoom-out limit for
5754 // ChartDummy.
5755 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5756
5757 if (m_true_scale_ppm)
5758 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5759 else
5760 VPoint.chart_scale = 1.0;
5761
5762 // Create a nice renderable string
5763 double round_factor = 1000.;
5764 if (VPoint.chart_scale <= 1000.)
5765 round_factor = 10.;
5766 else if (VPoint.chart_scale <= 10000.)
5767 round_factor = 100.;
5768 else if (VPoint.chart_scale <= 100000.)
5769 round_factor = 1000.;
5770
5771 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5772 double retina_coef = 1;
5773#ifdef ocpnUSE_GL
5774#ifdef __WXOSX__
5775 if (g_bopengl) {
5776 retina_coef = GetContentScaleFactor();
5777 }
5778#endif
5779#endif
5780
5781 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5782 // rounded to the nearest 10, 100 or 1000.
5783 //
5784 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5785 // true_scale_display. That does not make sense. The chart scale should be
5786 // the same as the true scale within the limits of the rounding factor.
5787 double true_scale_display =
5788 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5789 wxString text;
5790
5791 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5792
5793 if (m_displayed_scale_factor > 10.0)
5794 text.Printf(_T("%s %4.0f (%1.0fx)"), _("Scale"), true_scale_display,
5795 m_displayed_scale_factor);
5796 else if (m_displayed_scale_factor > 1.0)
5797 text.Printf(_T("%s %4.0f (%1.1fx)"), _("Scale"), true_scale_display,
5798 m_displayed_scale_factor);
5799 else if (m_displayed_scale_factor > 0.1) {
5800 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5801 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5802 } else if (m_displayed_scale_factor > 0.01) {
5803 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5804 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5805 } else {
5806 text.Printf(
5807 _T("%s %4.0f (---)"), _("Scale"),
5808 true_scale_display); // Generally, no chart, so no chart scale factor
5809 }
5810
5811 m_scaleValue = true_scale_display;
5812 m_scaleText = text;
5813 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5814
5815 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5816 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5817 // Check to see if the text will fit in the StatusBar field...
5818 bool b_noshow = false;
5819 {
5820 int w = 0;
5821 int h;
5822 wxClientDC dc(parent_frame->GetStatusBar());
5823 if (dc.IsOk()) {
5824 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5825 dc.SetFont(*templateFont);
5826 dc.GetTextExtent(text, &w, &h);
5827
5828 // If text is too long for the allocated field, try to reduce the text
5829 // string a bit.
5830 wxRect rect;
5831 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5832 if (w && w > rect.width) {
5833 text.Printf(_T("%s (%1.1fx)"), _("Scale"),
5834 m_displayed_scale_factor);
5835 }
5836
5837 // Test again...if too big still, then give it up.
5838 dc.GetTextExtent(text, &w, &h);
5839
5840 if (w && w > rect.width) {
5841 b_noshow = true;
5842 }
5843 }
5844 }
5845
5846 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5847 }
5848 }
5849
5850 // Maintain member vLat/vLon
5851 m_vLat = VPoint.clat;
5852 m_vLon = VPoint.clon;
5853
5854 return b_ret;
5855}
5856
5857// Static Icon definitions for some symbols requiring
5858// scaling/rotation/translation Very specific wxDC draw commands are
5859// necessary to properly render these icons...See the code in
5860// ShipDraw()
5861
5862// This icon was adapted and scaled from the S52 Presentation Library
5863// version 3_03.
5864// Symbol VECGND02
5865
5866static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5867
5868// This ownship icon was adapted and scaled from the S52 Presentation
5869// Library version 3_03 Symbol OWNSHP05
5870static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5871 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5872
5873wxColour ChartCanvas::PredColor() {
5874 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5875 // visibility.
5876 if (SHIP_NORMAL == m_ownship_state)
5877 return GetGlobalColor(_T ( "URED" ));
5878
5879 else if (SHIP_LOWACCURACY == m_ownship_state)
5880 return GetGlobalColor(_T ( "YELO1" ));
5881
5882 return GetGlobalColor(_T ( "NODTA" ));
5883}
5884
5885wxColour ChartCanvas::ShipColor() {
5886 // Establish ship color
5887 // It changes color based on GPS and Chart accuracy/availability
5888
5889 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor(_T ( "GREY1" ));
5890
5891 if (SHIP_LOWACCURACY == m_ownship_state)
5892 return GetGlobalColor(_T ( "YELO1" ));
5893
5894 return GetGlobalColor(_T ( "URED" )); // default is OK
5895}
5896
5897void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5898 wxPoint2DDouble lShipMidPoint) {
5899 dc.SetPen(wxPen(PredColor(), 2));
5900
5901 if (SHIP_NORMAL == m_ownship_state)
5902 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5903 else
5904 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "YELO1" ))));
5905
5906 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5907 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5908
5909 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5910 lShipMidPoint.m_y);
5911 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5912 lShipMidPoint.m_y + 12);
5913}
5914
5915void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5916 wxPoint GPSOffsetPixels,
5917 wxPoint2DDouble lGPSPoint) {
5918 if (m_animationActive) return;
5919 // Develop a uniform length for course predictor line dash length, based on
5920 // physical display size Use this reference length to size all other graphics
5921 // elements
5922 float ref_dim = m_display_size_mm / 24;
5923 ref_dim = wxMin(ref_dim, 12);
5924 ref_dim = wxMax(ref_dim, 6);
5925
5926 wxColour cPred;
5927 cPred.Set(g_cog_predictor_color);
5928 if (cPred == wxNullColour) cPred = PredColor();
5929
5930 // Establish some graphic element line widths dependent on the platform
5931 // display resolution
5932 // double nominal_line_width_pix = wxMax(1.0,
5933 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5934 // not less than 1 pixel
5935 double nominal_line_width_pix = wxMax(
5936 1.0,
5937 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5938
5939 // If the calculated value is greater than the config file spec value, then
5940 // use it.
5941 if (nominal_line_width_pix > g_cog_predictor_width)
5942 g_cog_predictor_width = nominal_line_width_pix;
5943
5944 // Calculate ownship Position Predictor
5945 wxPoint lPredPoint, lHeadPoint;
5946
5947 float pCog = std::isnan(gCog) ? 0 : gCog;
5948 float pSog = std::isnan(gSog) ? 0 : gSog;
5949
5950 double pred_lat, pred_lon;
5951 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5952 &pred_lat, &pred_lon);
5953 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5954
5955 // test to catch the case where COG/HDG line crosses the screen
5956 LLBBox box;
5957
5958 // Should we draw the Head vector?
5959 // Compare the points lHeadPoint and lPredPoint
5960 // If they differ by more than n pixels, and the head vector is valid, then
5961 // render the head vector
5962
5963 float ndelta_pix = 10.;
5964 double hdg_pred_lat, hdg_pred_lon;
5965 bool b_render_hdt = false;
5966 if (!std::isnan(gHdt)) {
5967 // Calculate ownship Heading pointer as a predictor
5968 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5969 &hdg_pred_lon);
5970 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5971 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5972 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5973 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5974 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5975 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5976 }
5977 }
5978
5979 // draw course over ground if they are longer than the ship
5980 wxPoint lShipMidPoint;
5981 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5982 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5983 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5984 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5985
5986 if (lpp >= img_height / 2) {
5987 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5988 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5989 !std::isnan(gSog)) {
5990 // COG Predictor
5991 float dash_length = ref_dim;
5992 wxDash dash_long[2];
5993 dash_long[0] =
5994 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
5995 g_cog_predictor_width); // Long dash , in mm <---------+
5996 dash_long[1] = dash_long[0] / 2.0; // Short gap
5997
5998 // On ultra-hi-res displays, do not allow the dashes to be greater than
5999 // 250, since it is defined as (char)
6000 if (dash_length > 250.) {
6001 dash_long[0] = 250. / g_cog_predictor_width;
6002 dash_long[1] = dash_long[0] / 2;
6003 }
6004
6005 wxPen ppPen2(cPred, g_cog_predictor_width,
6006 (wxPenStyle)g_cog_predictor_style);
6007 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6008 ppPen2.SetDashes(2, dash_long);
6009 dc.SetPen(ppPen2);
6010 dc.StrokeLine(
6011 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6012 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6013
6014 if (g_cog_predictor_width > 1) {
6015 float line_width = g_cog_predictor_width / 3.;
6016
6017 wxDash dash_long3[2];
6018 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6019 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6020
6021 wxPen ppPen3(GetGlobalColor(_T ( "UBLCK" )), wxMax(1, line_width),
6022 (wxPenStyle)g_cog_predictor_style);
6023 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6024 ppPen3.SetDashes(2, dash_long3);
6025 dc.SetPen(ppPen3);
6026 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6027 lGPSPoint.m_y + GPSOffsetPixels.y,
6028 lPredPoint.x + GPSOffsetPixels.x,
6029 lPredPoint.y + GPSOffsetPixels.y);
6030 }
6031
6032 if (g_cog_predictor_endmarker) {
6033 // Prepare COG predictor endpoint icon
6034 double png_pred_icon_scale_factor = .4;
6035 if (g_ShipScaleFactorExp > 1.0)
6036 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6037 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6038
6039 wxPoint icon[4];
6040
6041 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6042 (float)(lPredPoint.x - lShipMidPoint.x));
6043 cog_rad += (float)PI;
6044
6045 for (int i = 0; i < 4; i++) {
6046 int j = i * 2;
6047 double pxa = (double)(s_png_pred_icon[j]);
6048 double pya = (double)(s_png_pred_icon[j + 1]);
6049
6050 pya *= png_pred_icon_scale_factor;
6051 pxa *= png_pred_icon_scale_factor;
6052
6053 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6054 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6055
6056 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6057 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6058 }
6059
6060 // Render COG endpoint icon
6061 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), g_cog_predictor_width / 2,
6062 wxPENSTYLE_SOLID);
6063 dc.SetPen(ppPen1);
6064 dc.SetBrush(wxBrush(cPred));
6065
6066 dc.StrokePolygon(4, icon);
6067 }
6068 }
6069 }
6070
6071 // HDT Predictor
6072 if (b_render_hdt) {
6073 float hdt_dash_length = ref_dim * 0.4;
6074
6075 cPred.Set(g_ownship_HDTpredictor_color);
6076 if (cPred == wxNullColour) cPred = PredColor();
6077 float hdt_width =
6078 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6079 : g_cog_predictor_width * 0.8);
6080 wxDash dash_short[2];
6081 dash_short[0] =
6082 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6083 hdt_width); // Short dash , in mm <---------+
6084 dash_short[1] =
6085 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6086 hdt_width); // Short gap |
6087
6088 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6089 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6090 ppPen2.SetDashes(2, dash_short);
6091
6092 dc.SetPen(ppPen2);
6093 dc.StrokeLine(
6094 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6095 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6096
6097 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6098 dc.SetPen(ppPen1);
6099 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "GREY2" ))));
6100
6101 if (g_ownship_HDTpredictor_endmarker) {
6102 double nominal_circle_size_pixels = wxMax(
6103 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6104
6105 // Scale the circle to ChartScaleFactor, slightly softened....
6106 if (g_ShipScaleFactorExp > 1.0)
6107 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6108
6109 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6110 lHeadPoint.y + GPSOffsetPixels.y,
6111 nominal_circle_size_pixels / 2);
6112 }
6113 }
6114
6115 // Draw radar rings if activated
6116 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6117 double factor = 1.00;
6118 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6119 factor = 1 / 1.852;
6120 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6121 if (std::isnan(gSog))
6122 factor = 0.0;
6123 else
6124 factor = gSog / 60;
6125 }
6126 factor *= g_fNavAidRadarRingsStep;
6127
6128 double tlat, tlon;
6129 wxPoint r;
6130 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6131 GetCanvasPointPix(tlat, tlon, &r);
6132
6133 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6134 pow((double)(lGPSPoint.m_y - r.y), 2));
6135 int pix_radius = (int)lpp;
6136
6137 extern wxColor GetDimColor(wxColor c);
6138 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6139
6140 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6141
6142 dc.SetPen(ppPen1);
6143 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6144
6145 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6146 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6147 }
6148}
6149
6150void ChartCanvas::ComputeShipScaleFactor(
6151 float icon_hdt, int ownShipWidth, int ownShipLength,
6152 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6153 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6154 float screenResolution = m_pix_per_mm;
6155
6156 // Calculate the true ship length in exact pixels
6157 double ship_bow_lat, ship_bow_lon;
6158 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6159 &ship_bow_lat, &ship_bow_lon);
6160 wxPoint lShipBowPoint;
6161 wxPoint2DDouble b_point =
6162 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6163 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6164
6165 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6166 powf((float)(b_point.m_y - a_point.m_y), 2));
6167
6168 // And in mm
6169 float shipLength_mm = shipLength_px / screenResolution;
6170
6171 // Set minimum ownship drawing size
6172 float ownship_min_mm = g_n_ownship_min_mm;
6173 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6174
6175 // Calculate Nautical Miles distance from midships to gps antenna
6176 float hdt_ant = icon_hdt + 180.;
6177 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6178 float dx = g_n_gps_antenna_offset_x / 1852.;
6179 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6180 {
6181 hdt_ant = icon_hdt;
6182 dy = -dy;
6183 }
6184
6185 // If the drawn ship size is going to be clamped, adjust the gps antenna
6186 // offsets
6187 if (shipLength_mm < ownship_min_mm) {
6188 dy /= shipLength_mm / ownship_min_mm;
6189 dx /= shipLength_mm / ownship_min_mm;
6190 }
6191
6192 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6193
6194 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6195 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6196 &ship_mid_lon1);
6197
6198 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6199 &lShipMidPoint);
6200
6201 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6202 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6203
6204 float scale_factor = shipLength_px / ownShipLength;
6205
6206 // Calculate a scale factor that would produce a reasonably sized icon
6207 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6208
6209 // And choose the correct one
6210 scale_factor = wxMax(scale_factor, scale_factor_min);
6211
6212 scale_factor_y = scale_factor;
6213 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6214 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6215}
6216
6217void ChartCanvas::ShipDraw(ocpnDC &dc) {
6218 if (!GetVP().IsValid()) return;
6219
6220 wxPoint GPSOffsetPixels(0, 0);
6221 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6222
6223 // COG/SOG may be undefined in NMEA data stream
6224 float pCog = std::isnan(gCog) ? 0 : gCog;
6225 float pSog = std::isnan(gSog) ? 0 : gSog;
6226
6227 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6228
6229 lShipMidPoint = lGPSPoint;
6230
6231 // Draw the icon rotated to the COG
6232 // or to the Hdt if available
6233 float icon_hdt = pCog;
6234 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6235
6236 // COG may be undefined in NMEA data stream
6237 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6238
6239 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6240 // predictor
6241 double osd_head_lat, osd_head_lon;
6242 wxPoint osd_head_point;
6243
6244 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6245 &osd_head_lon);
6246
6247 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6248
6249 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6250 (float)(osd_head_point.x - lShipMidPoint.m_x));
6251 icon_rad += (float)PI;
6252
6253 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6254
6255 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6256 // nominal size and is just barely outside the viewport ....
6257 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6258
6259 // TODO: fix to include actual size of boat that will be rendered
6260 int img_height = 0;
6261 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6262 if (GetVP().chart_scale >
6263 300000) // According to S52, this should be 50,000
6264 {
6265 ShipDrawLargeScale(dc, lShipMidPoint);
6266 img_height = 20;
6267 } else {
6268 wxImage pos_image;
6269
6270 // Substitute user ownship image if found
6271 if (m_pos_image_user)
6272 pos_image = m_pos_image_user->Copy();
6273 else if (SHIP_NORMAL == m_ownship_state)
6274 pos_image = m_pos_image_red->Copy();
6275 if (SHIP_LOWACCURACY == m_ownship_state)
6276 pos_image = m_pos_image_yellow->Copy();
6277 else if (SHIP_NORMAL != m_ownship_state)
6278 pos_image = m_pos_image_grey->Copy();
6279
6280 // Substitute user ownship image if found
6281 if (m_pos_image_user) {
6282 pos_image = m_pos_image_user->Copy();
6283
6284 if (SHIP_LOWACCURACY == m_ownship_state)
6285 pos_image = m_pos_image_user_yellow->Copy();
6286 else if (SHIP_NORMAL != m_ownship_state)
6287 pos_image = m_pos_image_user_grey->Copy();
6288 }
6289
6290 img_height = pos_image.GetHeight();
6291
6292 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6293 g_OwnShipIconType > 0) // use large ship
6294 {
6295 int ownShipWidth = 22; // Default values from s_ownship_icon
6296 int ownShipLength = 84;
6297 if (g_OwnShipIconType == 1) {
6298 ownShipWidth = pos_image.GetWidth();
6299 ownShipLength = pos_image.GetHeight();
6300 }
6301
6302 float scale_factor_x, scale_factor_y;
6303 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6304 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6305 scale_factor_x, scale_factor_y);
6306
6307 if (g_OwnShipIconType == 1) { // Scaled bitmap
6308 pos_image.Rescale(ownShipWidth * scale_factor_x,
6309 ownShipLength * scale_factor_y,
6310 wxIMAGE_QUALITY_HIGH);
6311 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6312 wxImage rot_image =
6313 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6314
6315 // Simple sharpening algorithm.....
6316 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6317 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6318 if (rot_image.GetAlpha(ip, jp) > 64)
6319 rot_image.SetAlpha(ip, jp, 255);
6320
6321 wxBitmap os_bm(rot_image);
6322
6323 int w = os_bm.GetWidth();
6324 int h = os_bm.GetHeight();
6325 img_height = h;
6326
6327 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6328 lShipMidPoint.m_y - h / 2, true);
6329
6330 // Maintain dirty box,, missing in __WXMSW__ library
6331 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6332 lShipMidPoint.m_y - h / 2);
6333 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6334 lShipMidPoint.m_y - h / 2 + h);
6335 }
6336
6337 else if (g_OwnShipIconType == 2) { // Scaled Vector
6338 wxPoint ownship_icon[10];
6339
6340 for (int i = 0; i < 10; i++) {
6341 int j = i * 2;
6342 float pxa = (float)(s_ownship_icon[j]);
6343 float pya = (float)(s_ownship_icon[j + 1]);
6344 pya *= scale_factor_y;
6345 pxa *= scale_factor_x;
6346
6347 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6348 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6349
6350 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6351 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6352 }
6353
6354 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), 1, wxPENSTYLE_SOLID);
6355 dc.SetPen(ppPen1);
6356 dc.SetBrush(wxBrush(ShipColor()));
6357
6358 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6359
6360 // draw reference point (midships) cross
6361 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6362 ownship_icon[7].y);
6363 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6364 ownship_icon[9].y);
6365 }
6366
6367 img_height = ownShipLength * scale_factor_y;
6368
6369 // Reference point, where the GPS antenna is
6370 int circle_rad = 3;
6371 if (m_pos_image_user) circle_rad = 1;
6372
6373 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6374 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6375 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6376 } else { // Fixed bitmap icon.
6377 /* non opengl, or suboptimal opengl via ocpndc: */
6378 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6379 wxImage rot_image =
6380 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6381
6382 // Simple sharpening algorithm.....
6383 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6384 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6385 if (rot_image.GetAlpha(ip, jp) > 64)
6386 rot_image.SetAlpha(ip, jp, 255);
6387
6388 wxBitmap os_bm(rot_image);
6389
6390 if (g_ShipScaleFactorExp > 1) {
6391 wxImage scaled_image = os_bm.ConvertToImage();
6392 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6393 1.0; // soften the scale factor a bit
6394 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6395 scaled_image.GetHeight() * factor,
6396 wxIMAGE_QUALITY_HIGH));
6397 }
6398 int w = os_bm.GetWidth();
6399 int h = os_bm.GetHeight();
6400 img_height = h;
6401
6402 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6403 lShipMidPoint.m_y - h / 2, true);
6404
6405 // Reference point, where the GPS antenna is
6406 int circle_rad = 3;
6407 if (m_pos_image_user) circle_rad = 1;
6408
6409 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6410 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6411 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6412
6413 // Maintain dirty box,, missing in __WXMSW__ library
6414 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6415 lShipMidPoint.m_y - h / 2);
6416 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6417 lShipMidPoint.m_y - h / 2 + h);
6418 }
6419 } // ownship draw
6420 }
6421
6422 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6423}
6424
6425/* @ChartCanvas::CalcGridSpacing
6426 **
6427 ** Calculate the major and minor spacing between the lat/lon grid
6428 **
6429 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6430 *window
6431 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6432 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6433 ** @return [void]
6434 */
6435void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6436 float &MinorSpacing) {
6437 // table for calculating the distance between the grids
6438 // [0] view_scale ppm
6439 // [1] spacing between major grid lines in degrees
6440 // [2] spacing between minor grid lines in degrees
6441 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6442 {.000001f, 45.0f, 15.0f},
6443 {.0002f, 30.0f, 10.0f},
6444 {.0003f, 10.0f, 2.0f},
6445 {.0008f, 5.0f, 1.0f},
6446 {.001f, 2.0f, 30.0f / 60.0f},
6447 {.003f, 1.0f, 20.0f / 60.0f},
6448 {.006f, 0.5f, 10.0f / 60.0f},
6449 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6450 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6451 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6452 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6453 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6454 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6455 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6456 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6457
6458 unsigned int tabi;
6459 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6460 if (view_scale_ppm < lltab[tabi][0]) break;
6461 MajorSpacing = lltab[tabi][1]; // major latitude distance
6462 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6463 return;
6464}
6465/* @ChartCanvas::CalcGridText *************************************
6466 **
6467 ** Calculates text to display at the major grid lines
6468 **
6469 ** @param [r] latlon [float] latitude or longitude of grid line
6470 ** @param [r] spacing [float] distance between two major grid lines
6471 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6472 **
6473 ** @return
6474 */
6475
6476wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6477 int deg = (int)fabs(latlon); // degrees
6478 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6479 char postfix;
6480
6481 // calculate postfix letter (NSEW)
6482 if (latlon > 0.0) {
6483 if (bPostfix) {
6484 postfix = 'N';
6485 } else {
6486 postfix = 'E';
6487 }
6488 } else if (latlon < 0.0) {
6489 if (bPostfix) {
6490 postfix = 'S';
6491 } else {
6492 postfix = 'W';
6493 }
6494 } else {
6495 postfix = ' '; // no postfix for equator and greenwich
6496 }
6497 // calculate text, display minutes only if spacing is smaller than one degree
6498
6499 wxString ret;
6500 if (spacing >= 1.0) {
6501 ret.Printf(_T("%3d%c %c"), deg, 0x00b0, postfix);
6502 } else if (spacing >= (1.0 / 60.0)) {
6503 ret.Printf(_T("%3d%c%02.0f %c"), deg, 0x00b0, min, postfix);
6504 } else {
6505 ret.Printf(_T("%3d%c%02.2f %c"), deg, 0x00b0, min, postfix);
6506 }
6507
6508 return ret;
6509}
6510
6511/* @ChartCanvas::GridDraw *****************************************
6512 **
6513 ** Draws major and minor Lat/Lon Grid on the chart
6514 ** - distance between Grid-lm ines are calculated automatic
6515 ** - major grid lines will be across the whole chart window
6516 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6517 **
6518 ** @param [w] dc [wxDC&] the wx drawing context
6519 **
6520 ** @return [void]
6521 ************************************************************************/
6522void ChartCanvas::GridDraw(ocpnDC &dc) {
6523 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6524
6525 double nlat, elon, slat, wlon;
6526 float lat, lon;
6527 float dlon;
6528 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6529 wxCoord w, h;
6530 wxPen GridPen(GetGlobalColor(_T ( "SNDG1" )), 1, wxPENSTYLE_SOLID);
6531 dc.SetPen(GridPen);
6532 dc.SetFont(*m_pgridFont);
6533 dc.SetTextForeground(GetGlobalColor(_T ( "SNDG1" )));
6534
6535 w = m_canvas_width;
6536 h = m_canvas_height;
6537
6538 GetCanvasPixPoint(0, 0, nlat,
6539 wlon); // get lat/lon of upper left point of the window
6540 GetCanvasPixPoint(w, h, slat,
6541 elon); // get lat/lon of lower right point of the window
6542 dlon =
6543 elon -
6544 wlon; // calculate how many degrees of longitude are shown in the window
6545 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6546 {
6547 dlon = dlon + 360.0;
6548 }
6549 // calculate distance between latitude grid lines
6550 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6551
6552 // calculate position of first major latitude grid line
6553 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6554
6555 // Draw Major latitude grid lines and text
6556 while (lat < nlat) {
6557 wxPoint r;
6558 wxString st =
6559 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6560 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6561 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6562 dc.DrawText(st, 0, r.y); // draw text
6563 lat = lat + gridlatMajor;
6564
6565 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6566 }
6567
6568 // calculate position of first minor latitude grid line
6569 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6570
6571 // Draw minor latitude grid lines
6572 while (lat < nlat) {
6573 wxPoint r;
6574 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6575 dc.DrawLine(0, r.y, 10, r.y, false);
6576 dc.DrawLine(w - 10, r.y, w, r.y, false);
6577 lat = lat + gridlatMinor;
6578 }
6579
6580 // calculate distance between grid lines
6581 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6582
6583 // calculate position of first major latitude grid line
6584 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6585
6586 // draw major longitude grid lines
6587 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6588 wxPoint r;
6589 wxString st = CalcGridText(lon, gridlonMajor, false);
6590 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6591 dc.DrawLine(r.x, 0, r.x, h, false);
6592 dc.DrawText(st, r.x, 0);
6593 lon = lon + gridlonMajor;
6594 if (lon > 180.0) {
6595 lon = lon - 360.0;
6596 }
6597
6598 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6599 }
6600
6601 // calculate position of first minor longitude grid line
6602 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6603 // draw minor longitude grid lines
6604 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6605 wxPoint r;
6606 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6607 dc.DrawLine(r.x, 0, r.x, 10, false);
6608 dc.DrawLine(r.x, h - 10, r.x, h, false);
6609 lon = lon + gridlonMinor;
6610 if (lon > 180.0) {
6611 lon = lon - 360.0;
6612 }
6613 }
6614}
6615
6616void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6617 if (0 ) {
6618 double blat, blon, tlat, tlon;
6619 wxPoint r;
6620
6621 int x_origin = m_bDisplayGrid ? 60 : 20;
6622 int y_origin = m_canvas_height - 50;
6623
6624 float dist;
6625 int count;
6626 wxPen pen1, pen2;
6627
6628 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6629 {
6630 dist = 10.0;
6631 count = 5;
6632 pen1 = wxPen(GetGlobalColor(_T ( "SNDG2" )), 3, wxPENSTYLE_SOLID);
6633 pen2 = wxPen(GetGlobalColor(_T ( "SNDG1" )), 3, wxPENSTYLE_SOLID);
6634 } else // Draw 1 mile scale as SCALEB10
6635 {
6636 dist = 1.0;
6637 count = 10;
6638 pen1 = wxPen(GetGlobalColor(_T ( "SCLBR" )), 3, wxPENSTYLE_SOLID);
6639 pen2 = wxPen(GetGlobalColor(_T ( "CHGRD" )), 3, wxPENSTYLE_SOLID);
6640 }
6641
6642 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6643 double rotation = -VPoint.rotation;
6644
6645 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6646 GetCanvasPointPix(tlat, tlon, &r);
6647 int l1 = (y_origin - r.y) / count;
6648
6649 for (int i = 0; i < count; i++) {
6650 int y = l1 * i;
6651 if (i & 1)
6652 dc.SetPen(pen1);
6653 else
6654 dc.SetPen(pen2);
6655
6656 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6657 }
6658 } else {
6659 double blat, blon, tlat, tlon;
6660
6661 int x_origin = 5.0 * GetPixPerMM();
6662 int chartbar_height = GetChartbarHeight();
6663 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6664 // if (style->chartStatusWindowTransparent)
6665 // chartbar_height = 0;
6666 int y_origin = m_canvas_height - chartbar_height - 5;
6667#ifdef __WXOSX__
6668 if (!g_bopengl)
6669 y_origin =
6670 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6671#endif
6672
6673 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6674 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6675
6676 double d;
6677 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6678 d /= 2;
6679
6680 int unit = g_iDistanceFormat;
6681 if (d < .5 &&
6682 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6683 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6684
6685 // nice number
6686 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6687 float places = floor(logdist), rem = logdist - places;
6688 dist = pow(10, places);
6689
6690 if (rem < .2)
6691 dist /= 5;
6692 else if (rem < .5)
6693 dist /= 2;
6694
6695 wxString s = wxString::Format(_T("%g "), dist) + getUsrDistanceUnit(unit);
6696 wxPen pen1 = wxPen(GetGlobalColor(_T ( "UBLCK" )), 3, wxPENSTYLE_SOLID);
6697 double rotation = -VPoint.rotation;
6698
6699 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6700 &tlat, &tlon);
6701 wxPoint r;
6702 GetCanvasPointPix(tlat, tlon, &r);
6703 int l1 = r.x - x_origin;
6704
6705 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6706 12); // Store this for later reference
6707
6708 dc.SetPen(pen1);
6709
6710 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6711 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6712 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6713
6714 dc.SetFont(*m_pgridFont);
6715 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
6716 int w, h;
6717 dc.GetTextExtent(s, &w, &h);
6718 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6719 }
6720}
6721
6722void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6723 // Constants?
6724 double da_min = 2.;
6725 double da_max = 6.;
6726 double ra_min = 0.;
6727 double ra_max = 40.;
6728
6729 wxPen pen_save = dc.GetPen();
6730
6731 wxDateTime now = wxDateTime::Now();
6732
6733 dc.SetPen(pen);
6734
6735 int x0, y0, x1, y1;
6736
6737 x0 = x1 = x + radius; // Start point
6738 y0 = y1 = y;
6739 double angle = 0.;
6740 int i = 0;
6741
6742 while (angle < 360.) {
6743 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6744 angle += da;
6745
6746 if (angle > 360.) angle = 360.;
6747
6748 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6749
6750 double r;
6751 if (i & 1)
6752 r = radius + ra;
6753 else
6754 r = radius - ra;
6755
6756 x1 = (int)(x + cos(angle * PI / 180.) * r);
6757 y1 = (int)(y + sin(angle * PI / 180.) * r);
6758
6759 dc.DrawLine(x0, y0, x1, y1);
6760
6761 x0 = x1;
6762 y0 = y1;
6763
6764 i++;
6765 }
6766
6767 dc.DrawLine(x + radius, y, x1, y1); // closure
6768
6769 dc.SetPen(pen_save);
6770}
6771
6772static bool bAnchorSoundPlaying = false;
6773
6774static void onAnchorSoundFinished(void *ptr) {
6775 g_anchorwatch_sound->UnLoad();
6776 bAnchorSoundPlaying = false;
6777}
6778
6779void ChartCanvas::AlertDraw(ocpnDC &dc) {
6780 // Visual and audio alert for anchorwatch goes here
6781 bool play_sound = false;
6782 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6783 if (AnchorAlertOn1) {
6784 wxPoint TargetPoint;
6785 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
6786 &TargetPoint);
6787 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6788 TargetPoint.y, 100);
6789 play_sound = true;
6790 }
6791 } else
6792 AnchorAlertOn1 = false;
6793
6794 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6795 if (AnchorAlertOn2) {
6796 wxPoint TargetPoint;
6797 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
6798 &TargetPoint);
6799 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6800 TargetPoint.y, 100);
6801 play_sound = true;
6802 }
6803 } else
6804 AnchorAlertOn2 = false;
6805
6806 if (play_sound) {
6807 if (!bAnchorSoundPlaying) {
6808 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6809 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6810 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6811 if (g_anchorwatch_sound->IsOk()) {
6812 bAnchorSoundPlaying = true;
6813 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6814 g_anchorwatch_sound->Play();
6815 }
6816 }
6817 }
6818}
6819
6820void ChartCanvas::UpdateShips() {
6821 // Get the rectangle in the current dc which bounds the "ownship" symbol
6822
6823 wxClientDC dc(this);
6824 if (!dc.IsOk()) return;
6825
6826 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6827 if (!test_bitmap.IsOk()) return;
6828
6829 wxMemoryDC temp_dc(test_bitmap);
6830
6831 temp_dc.ResetBoundingBox();
6832 temp_dc.DestroyClippingRegion();
6833 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6834
6835 // Draw the ownship on the temp_dc
6836 ocpnDC ocpndc = ocpnDC(temp_dc);
6837 ShipDraw(ocpndc);
6838
6839 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6840 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6841 if (p) {
6842 wxPoint px;
6843 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6844 ocpndc.CalcBoundingBox(px.x, px.y);
6845 }
6846 }
6847
6848 ship_draw_rect =
6849 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6850 temp_dc.MaxY() - temp_dc.MinY());
6851
6852 wxRect own_ship_update_rect = ship_draw_rect;
6853
6854 if (!own_ship_update_rect.IsEmpty()) {
6855 // The required invalidate rectangle is the union of the last drawn
6856 // rectangle and this drawn rectangle
6857 own_ship_update_rect.Union(ship_draw_last_rect);
6858 own_ship_update_rect.Inflate(2);
6859 }
6860
6861 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6862
6863 ship_draw_last_rect = ship_draw_rect;
6864
6865 temp_dc.SelectObject(wxNullBitmap);
6866}
6867
6868void ChartCanvas::UpdateAlerts() {
6869 // Get the rectangle in the current dc which bounds the detected Alert
6870 // targets
6871
6872 // Use this dc
6873 wxClientDC dc(this);
6874
6875 // Get dc boundary
6876 int sx, sy;
6877 dc.GetSize(&sx, &sy);
6878
6879 // Need a bitmap
6880 wxBitmap test_bitmap(sx, sy, -1);
6881
6882 // Create a memory DC
6883 wxMemoryDC temp_dc;
6884 temp_dc.SelectObject(test_bitmap);
6885
6886 temp_dc.ResetBoundingBox();
6887 temp_dc.DestroyClippingRegion();
6888 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6889
6890 // Draw the Alert Targets on the temp_dc
6891 ocpnDC ocpndc = ocpnDC(temp_dc);
6892 AlertDraw(ocpndc);
6893
6894 // Retrieve the drawing extents
6895 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6896 temp_dc.MaxX() - temp_dc.MinX(),
6897 temp_dc.MaxY() - temp_dc.MinY());
6898
6899 if (!alert_rect.IsEmpty())
6900 alert_rect.Inflate(2); // clear all drawing artifacts
6901
6902 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6903 // The required invalidate rectangle is the union of the last drawn
6904 // rectangle and this drawn rectangle
6905 wxRect alert_update_rect = alert_draw_rect;
6906 alert_update_rect.Union(alert_rect);
6907
6908 // Invalidate the rectangular region
6909 RefreshRect(alert_update_rect, false);
6910 }
6911
6912 // Save this rectangle for next time
6913 alert_draw_rect = alert_rect;
6914
6915 temp_dc.SelectObject(wxNullBitmap); // clean up
6916}
6917
6918void ChartCanvas::UpdateAIS() {
6919 if (!g_pAIS) return;
6920
6921 // Get the rectangle in the current dc which bounds the detected AIS targets
6922
6923 // Use this dc
6924 wxClientDC dc(this);
6925
6926 // Get dc boundary
6927 int sx, sy;
6928 dc.GetSize(&sx, &sy);
6929
6930 wxRect ais_rect;
6931
6932 // How many targets are there?
6933
6934 // If more than "some number", it will be cheaper to refresh the entire
6935 // screen than to build update rectangles for each target.
6936 if (g_pAIS->GetTargetList().size() > 10) {
6937 ais_rect = wxRect(0, 0, sx, sy); // full screen
6938 } else {
6939 // Need a bitmap
6940 wxBitmap test_bitmap(sx, sy, -1);
6941
6942 // Create a memory DC
6943 wxMemoryDC temp_dc;
6944 temp_dc.SelectObject(test_bitmap);
6945
6946 temp_dc.ResetBoundingBox();
6947 temp_dc.DestroyClippingRegion();
6948 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6949
6950 // Draw the AIS Targets on the temp_dc
6951 ocpnDC ocpndc = ocpnDC(temp_dc);
6952 AISDraw(ocpndc, GetVP(), this);
6953 AISDrawAreaNotices(ocpndc, GetVP(), this);
6954
6955 // Retrieve the drawing extents
6956 ais_rect =
6957 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6958 temp_dc.MaxY() - temp_dc.MinY());
6959
6960 if (!ais_rect.IsEmpty())
6961 ais_rect.Inflate(2); // clear all drawing artifacts
6962
6963 temp_dc.SelectObject(wxNullBitmap); // clean up
6964 }
6965
6966 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6967 // The required invalidate rectangle is the union of the last drawn
6968 // rectangle and this drawn rectangle
6969 wxRect ais_update_rect = ais_draw_rect;
6970 ais_update_rect.Union(ais_rect);
6971
6972 // Invalidate the rectangular region
6973 RefreshRect(ais_update_rect, false);
6974 }
6975
6976 // Save this rectangle for next time
6977 ais_draw_rect = ais_rect;
6978}
6979
6980void ChartCanvas::ToggleCPAWarn() {
6981 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6982 wxString mess;
6983 if (g_bCPAWarn) {
6984 g_bTCPA_Max = true;
6985 mess = _("ON");
6986 } else {
6987 g_bTCPA_Max = false;
6988 mess = _("OFF");
6989 }
6990 // Print to status bar if available.
6991 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
6992 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
6993 } else {
6994 if (!g_AisFirstTimeUse) {
6995 OCPNMessageBox(this,
6996 _("CPA Alarm is switched") + _T(" ") + mess.MakeLower(),
6997 _("CPA") + _T(" ") + mess, 4, 4);
6998 }
6999 }
7000}
7001
7002void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7003
7004void ChartCanvas::OnSize(wxSizeEvent &event) {
7005 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7006 // GetClientSize returns the size of the canvas area in logical pixels.
7007 GetClientSize(&m_canvas_width, &m_canvas_height);
7008
7009#ifdef __WXOSX__
7010 // Support scaled HDPI displays.
7011 m_displayScale = GetContentScaleFactor();
7012#endif
7013
7014 // Convert to physical pixels.
7015 m_canvas_width *= m_displayScale;
7016 m_canvas_height *= m_displayScale;
7017
7018 // Resize the current viewport
7019 VPoint.pix_width = m_canvas_width;
7020 VPoint.pix_height = m_canvas_height;
7021 VPoint.SetPixelScale(m_displayScale);
7022
7023 // Get some canvas metrics
7024
7025 // Rescale to current value, in order to rebuild VPoint data
7026 // structures for new canvas size
7028
7029 m_absolute_min_scale_ppm =
7030 m_canvas_width /
7031 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7032
7033 // Inform the parent Frame that I am being resized...
7034 gFrame->ProcessCanvasResize();
7035
7036 // if MUIBar is active, size the bar
7037 // if(g_useMUI && !m_muiBar){ // rebuild if
7038 // necessary
7039 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7040 // m_muiBarHOSize = m_muiBar->GetSize();
7041 // }
7042
7043 if (m_muiBar) {
7044 SetMUIBarPosition();
7045 UpdateFollowButtonState();
7046 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7047 }
7048
7049 // Set up the scroll margins
7050 xr_margin = m_canvas_width * 95 / 100;
7051 xl_margin = m_canvas_width * 5 / 100;
7052 yt_margin = m_canvas_height * 5 / 100;
7053 yb_margin = m_canvas_height * 95 / 100;
7054
7055 if (m_pQuilt)
7056 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7057
7058 // Resize the scratch BM
7059 delete pscratch_bm;
7060 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7061 m_brepaint_piano = true;
7062
7063 // Resize the Route Calculation BM
7064 m_dc_route.SelectObject(wxNullBitmap);
7065 delete proute_bm;
7066 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7067 m_dc_route.SelectObject(*proute_bm);
7068
7069 // Resize the saved Bitmap
7070 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7071
7072 // Resize the working Bitmap
7073 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7074
7075 // Rescale again, to capture all the changes for new canvas size
7077
7078#ifdef ocpnUSE_GL
7079 if (/*g_bopengl &&*/ m_glcc) {
7080 // FIXME (dave) This can go away?
7081 m_glcc->OnSize(event);
7082 }
7083#endif
7084
7085 FormatPianoKeys();
7086 // Invalidate the whole window
7087 ReloadVP();
7088}
7089
7090void ChartCanvas::ProcessNewGUIScale() {
7091 // m_muiBar->Hide();
7092 delete m_muiBar;
7093 m_muiBar = 0;
7094
7095 CreateMUIBar();
7096}
7097
7098void ChartCanvas::CreateMUIBar() {
7099 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7100
7101 // We need to update the m_bENCGroup flag, at least for the initial creation
7102 // of a MUIBar
7103 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7104
7105 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7106 m_muiBar->SetColorScheme(m_cs);
7107 m_muiBarHOSize = m_muiBar->m_size;
7108 }
7109
7110 if (m_muiBar) {
7111 SetMUIBarPosition();
7112 UpdateFollowButtonState();
7113 m_muiBar->UpdateDynamicValues();
7114 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7115 }
7116}
7117
7118void ChartCanvas::SetMUIBarPosition() {
7119 // if MUIBar is active, size the bar
7120 if (m_muiBar) {
7121 // We estimate the piano width based on the canvas width
7122 int pianoWidth = GetClientSize().x * 0.6f;
7123 // If the piano already exists, we can use its exact width
7124 // if(m_Piano)
7125 // pianoWidth = m_Piano->GetWidth();
7126
7127 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7128 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7129 delete m_muiBar;
7130 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7131 m_muiBar->SetColorScheme(m_cs);
7132 }
7133 }
7134
7135 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7136 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7137 delete m_muiBar;
7138 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7139 m_muiBar->SetColorScheme(m_cs);
7140 }
7141 }
7142
7143 m_muiBar->SetBestPosition();
7144 }
7145}
7146
7147void ChartCanvas::DestroyMuiBar() {
7148 if (m_muiBar) {
7149 delete m_muiBar;
7150 m_muiBar = NULL;
7151 }
7152}
7153
7154void ChartCanvas::ShowCompositeInfoWindow(
7155 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7156 if (n_charts > 0) {
7157 if (NULL == m_pCIWin) {
7158 m_pCIWin = new ChInfoWin(this);
7159 m_pCIWin->Hide();
7160 }
7161
7162 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7163 wxString s;
7164
7165 s = _("Composite of ");
7166
7167 wxString s1;
7168 s1.Printf("%d ", n_charts);
7169 if (n_charts > 1)
7170 s1 += _("charts");
7171 else
7172 s1 += _("chart");
7173 s += s1;
7174 s += '\n';
7175
7176 s1.Printf(_("Chart scale"));
7177 s1 += ": ";
7178 wxString s2;
7179 s2.Printf("1:%d\n", scale);
7180 s += s1;
7181 s += s2;
7182
7183 s1 = _("Zoom in for more information");
7184 s += s1;
7185 s += '\n';
7186
7187 int char_width = s1.Length();
7188 int char_height = 3;
7189
7190 if (g_bChartBarEx) {
7191 s += '\n';
7192 int j = 0;
7193 for (int i : index_vector) {
7194 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7195 wxString path = cte.GetFullSystemPath();
7196 s += path;
7197 s += '\n';
7198 char_height++;
7199 char_width = wxMax(char_width, path.Length());
7200 if (j++ >= 9) break;
7201 }
7202 if (j >= 9) {
7203 s += " .\n .\n .\n";
7204 char_height += 3;
7205 }
7206 s += '\n';
7207 char_height += 1;
7208
7209 char_width += 4; // Fluff
7210 }
7211
7212 m_pCIWin->SetString(s);
7213
7214 m_pCIWin->FitToChars(char_width, char_height);
7215
7216 wxPoint p;
7217 p.x = x / GetContentScaleFactor();
7218 if ((p.x + m_pCIWin->GetWinSize().x) >
7219 (m_canvas_width / GetContentScaleFactor()))
7220 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7221 m_pCIWin->GetWinSize().x) /
7222 2; // centered
7223
7224 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7225 4 - m_pCIWin->GetWinSize().y;
7226
7227 m_pCIWin->dbIndex = 0;
7228 m_pCIWin->chart_scale = 0;
7229 m_pCIWin->SetPosition(p);
7230 m_pCIWin->SetBitmap();
7231 m_pCIWin->Refresh();
7232 m_pCIWin->Show();
7233 }
7234 } else {
7235 HideChartInfoWindow();
7236 }
7237}
7238
7239void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7240 if (dbIndex >= 0) {
7241 if (NULL == m_pCIWin) {
7242 m_pCIWin = new ChInfoWin(this);
7243 m_pCIWin->Hide();
7244 }
7245
7246 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7247 wxString s;
7248 ChartBase *pc = NULL;
7249
7250 // TOCTOU race but worst case will reload chart.
7251 // need to lock it or the background spooler may evict charts in
7252 // OpenChartFromDBAndLock
7253 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7254 pc = ChartData->OpenChartFromDBAndLock(
7255 dbIndex, FULL_INIT); // this must come from cache
7256
7257 int char_width, char_height;
7258 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7259 if (pc) ChartData->UnLockCacheChart(dbIndex);
7260
7261 m_pCIWin->SetString(s);
7262 m_pCIWin->FitToChars(char_width, char_height);
7263
7264 wxPoint p;
7265 p.x = x / GetContentScaleFactor();
7266 if ((p.x + m_pCIWin->GetWinSize().x) >
7267 (m_canvas_width / GetContentScaleFactor()))
7268 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7269 m_pCIWin->GetWinSize().x) /
7270 2; // centered
7271
7272 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7273 4 - m_pCIWin->GetWinSize().y;
7274
7275 m_pCIWin->dbIndex = dbIndex;
7276 m_pCIWin->SetPosition(p);
7277 m_pCIWin->SetBitmap();
7278 m_pCIWin->Refresh();
7279 m_pCIWin->Show();
7280 }
7281 } else {
7282 HideChartInfoWindow();
7283 }
7284}
7285
7286void ChartCanvas::HideChartInfoWindow(void) {
7287 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7288 m_pCIWin->Hide();
7289 m_pCIWin->Destroy();
7290 m_pCIWin = NULL;
7291
7292#ifdef __ANDROID__
7293 androidForceFullRepaint();
7294#endif
7295 }
7296}
7297
7298void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7299 wxMouseEvent ev(wxEVT_MOTION);
7300 ev.m_x = mouse_x;
7301 ev.m_y = mouse_y;
7302 ev.m_leftDown = mouse_leftisdown;
7303
7304 wxEvtHandler *evthp = GetEventHandler();
7305
7306 ::wxPostEvent(evthp, ev);
7307}
7308
7309void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7310 if ((m_panx_target_final - m_panx_target_now) ||
7311 (m_pany_target_final - m_pany_target_now)) {
7312 DoTimedMovementTarget();
7313 } else
7314 DoTimedMovement();
7315}
7316
7317void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7318
7319bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7320 int delta) {
7321 if (m_disable_edge_pan) return false;
7322
7323 bool bft = false;
7324 int pan_margin = m_canvas_width * margin / 100;
7325 int pan_timer_set = 200;
7326 double pan_delta = GetVP().pix_width * delta / 100;
7327 int pan_x = 0;
7328 int pan_y = 0;
7329
7330 if (x > m_canvas_width - pan_margin) {
7331 bft = true;
7332 pan_x = pan_delta;
7333 }
7334
7335 else if (x < pan_margin) {
7336 bft = true;
7337 pan_x = -pan_delta;
7338 }
7339
7340 if (y < pan_margin) {
7341 bft = true;
7342 pan_y = -pan_delta;
7343 }
7344
7345 else if (y > m_canvas_height - pan_margin) {
7346 bft = true;
7347 pan_y = pan_delta;
7348 }
7349
7350 // Of course, if dragging, and the mouse left button is not down, we must
7351 // stop the event injection
7352 if (bdragging) {
7353 if (!g_btouch) {
7354 wxMouseState state = ::wxGetMouseState();
7355#if wxCHECK_VERSION(3, 0, 0)
7356 if (!state.LeftIsDown())
7357#else
7358 if (!state.LeftDown())
7359#endif
7360 bft = false;
7361 }
7362 }
7363 if ((bft) && !pPanTimer->IsRunning()) {
7364 PanCanvas(pan_x, pan_y);
7365 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7366 return true;
7367 }
7368
7369 // This mouse event must not be due to pan timer event injector
7370 // Mouse is out of the pan zone, so prevent any orphan event injection
7371 if ((!bft) && pPanTimer->IsRunning()) {
7372 pPanTimer->Stop();
7373 }
7374
7375 return (false);
7376}
7377
7378// Look for waypoints at the current position.
7379// Used to determine what a mouse event should act on.
7380
7381void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7382 bool setBeingEdited) {
7383 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7384 m_pRoutePointEditTarget = NULL;
7385 m_pFoundPoint = NULL;
7386
7387 SelectItem *pFind = NULL;
7388 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7389 SelectableItemList SelList = pSelect->FindSelectionList(
7390 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7391 wxSelectableItemListNode *node = SelList.GetFirst();
7392 while (node) {
7393 pFind = node->GetData();
7394
7395 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7396
7397 // Get an array of all routes using this point
7398 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7399 // TODO: delete m_pEditRouteArray after use?
7400
7401 // Use route array to determine actual visibility for the point
7402 bool brp_viz = false;
7403 if (m_pEditRouteArray) {
7404 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7405 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7406 if (pr->IsVisible()) {
7407 brp_viz = true;
7408 break;
7409 }
7410 }
7411 } else
7412 brp_viz = frp->IsVisible(); // isolated point
7413
7414 if (brp_viz) {
7415 // Use route array to rubberband all affected routes
7416 if (m_pEditRouteArray) // Editing Waypoint as part of route
7417 {
7418 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7419 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7420 pr->m_bIsBeingEdited = setBeingEdited;
7421 }
7422 m_bRouteEditing = setBeingEdited;
7423 } else // editing Mark
7424 {
7425 frp->m_bRPIsBeingEdited = setBeingEdited;
7426 m_bMarkEditing = setBeingEdited;
7427 }
7428
7429 m_pRoutePointEditTarget = frp;
7430 m_pFoundPoint = pFind;
7431 break; // out of the while(node)
7432 }
7433
7434 node = node->GetNext();
7435 } // while (node)
7436}
7437
7438void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7439 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7440 singleClickEventIsValid = false;
7441 m_DoubleClickTimer->Stop();
7442}
7443
7444bool leftIsDown;
7445
7446bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7447 if (!m_bChartDragging && !m_bDrawingRoute) {
7448 /*
7449 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7450 * mouse event coordinates are in logical pixels.
7451 */
7452 if (m_Compass && m_Compass->IsShown()) {
7453 wxRect logicalRect = m_Compass->GetLogicalRect();
7454 bool isInCompass = logicalRect.Contains(event.GetPosition());
7455 if (isInCompass) {
7456 if (m_Compass->MouseEvent(event)) {
7457 cursor_region = CENTER;
7458 if (!g_btouch) SetCanvasCursor(event);
7459 return true;
7460 }
7461 }
7462 }
7463
7464 if (m_notification_button && m_notification_button->IsShown()) {
7465 wxRect logicalRect = m_notification_button->GetLogicalRect();
7466 bool isinButton = logicalRect.Contains(event.GetPosition());
7467 if (isinButton) {
7468 SetCursor(*pCursorArrow);
7469 if (event.LeftDown()) HandleNotificationMouseClick();
7470 return true;
7471 }
7472 }
7473
7474 if (MouseEventToolbar(event)) return true;
7475
7476 if (MouseEventChartBar(event)) return true;
7477
7478 if (MouseEventMUIBar(event)) return true;
7479
7480 if (MouseEventIENCBar(event)) return true;
7481 }
7482 return false;
7483}
7484
7485void ChartCanvas::HandleNotificationMouseClick() {
7486 if (!m_NotificationsList) {
7487 m_NotificationsList = new NotificationsList(this);
7488
7489 // calculate best size for Notification list
7490
7491 wxPoint ClientUpperRight = ClientToScreen(wxPoint(GetSize().x, 0));
7492 wxPoint list_bottom = ClientToScreen(wxPoint(0, GetSize().y / 2));
7493 int size_y = list_bottom.y - (ClientUpperRight.y + 5);
7494 size_y -= GetCharHeight();
7495 size_y = wxMax(size_y, 200); // ensure always big enough to see
7496
7497 m_NotificationsList->SetSize(wxSize(GetCharWidth() * 80, size_y));
7498
7499 wxPoint m_currentNLPos = ClientToScreen(wxPoint(
7500 GetSize().x / 2, m_notification_button->GetRect().y +
7501 m_notification_button->GetRect().height + 5));
7502
7503 m_NotificationsList->Move(m_currentNLPos);
7504 m_NotificationsList->Hide();
7505 }
7506
7507 if (m_NotificationsList->IsShown()) {
7508 m_NotificationsList->Hide();
7509 } else {
7510 m_NotificationsList->ReloadNotificationList();
7511 m_NotificationsList->Show();
7512 }
7513}
7514bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7515 if (!g_bShowChartBar) return false;
7516
7517 if (!m_Piano->MouseEvent(event)) return false;
7518
7519 cursor_region = CENTER;
7520 if (!g_btouch) SetCanvasCursor(event);
7521 return true;
7522}
7523
7524bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7525 if (!IsPrimaryCanvas()) return false;
7526
7527 if (g_MainToolbar) {
7528 if (!g_MainToolbar->MouseEvent(event))
7529 return false;
7530 else
7531 g_MainToolbar->RefreshToolbar();
7532 }
7533
7534 cursor_region = CENTER;
7535 if (!g_btouch) SetCanvasCursor(event);
7536 return true;
7537}
7538
7539bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7540 if (!IsPrimaryCanvas()) return false;
7541
7542 if (g_iENCToolbar) {
7543 if (!g_iENCToolbar->MouseEvent(event))
7544 return false;
7545 else {
7546 g_iENCToolbar->RefreshToolbar();
7547 return true;
7548 }
7549 }
7550 return false;
7551}
7552
7553bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7554 if (m_muiBar) {
7555 if (!m_muiBar->MouseEvent(event)) return false;
7556 }
7557
7558 cursor_region = CENTER;
7559 if (!g_btouch) SetCanvasCursor(event);
7560 if (m_muiBar)
7561 return true;
7562 else
7563 return false;
7564}
7565
7566bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7567 int x, y;
7568
7569 bool bret = false;
7570
7571 event.GetPosition(&x, &y);
7572
7573 x *= m_displayScale;
7574 y *= m_displayScale;
7575
7576 m_MouseDragging = event.Dragging();
7577
7578 // Some systems produce null drag events, where the pointer position has not
7579 // changed from the previous value. Detect this case, and abort further
7580 // processing (FS#1748)
7581#ifdef __WXMSW__
7582 if (event.Dragging()) {
7583 if ((x == mouse_x) && (y == mouse_y)) return true;
7584 }
7585#endif
7586
7587 mouse_x = x;
7588 mouse_y = y;
7589 mouse_leftisdown = event.LeftDown();
7591
7592 // Establish the event region
7593 cursor_region = CENTER;
7594
7595 int chartbar_height = GetChartbarHeight();
7596
7597 if (m_Compass && m_Compass->IsShown() &&
7598 m_Compass->GetRect().Contains(event.GetPosition())) {
7599 cursor_region = CENTER;
7600 } else if (x > xr_margin) {
7601 cursor_region = MID_RIGHT;
7602 } else if (x < xl_margin) {
7603 cursor_region = MID_LEFT;
7604 } else if (y > yb_margin - chartbar_height &&
7605 y < m_canvas_height - chartbar_height) {
7606 cursor_region = MID_TOP;
7607 } else if (y < yt_margin) {
7608 cursor_region = MID_BOT;
7609 } else {
7610 cursor_region = CENTER;
7611 }
7612
7613 if (!g_btouch) SetCanvasCursor(event);
7614
7615 // Protect from leftUp's coming from event handlers in child
7616 // windows who return focus to the canvas.
7617 leftIsDown = event.LeftDown();
7618
7619#ifndef __WXOSX__
7620 if (event.LeftDown()) {
7621 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7622 // The menu bar is temporarily visible due to alt having been pressed.
7623 // Clicking will hide it, and do nothing else.
7624 g_bTempShowMenuBar = false;
7625 parent_frame->ApplyGlobalSettings(false);
7626 return (true);
7627 }
7628 }
7629#endif
7630
7631 // Update modifiers here; some window managers never send the key event
7632 m_modkeys = 0;
7633 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7634 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7635
7636#ifdef __WXMSW__
7637 // TODO Test carefully in other platforms, remove ifdef....
7638 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7639 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7640#endif
7641
7642 event.SetEventObject(this);
7643 if (SendMouseEventToPlugins(event))
7644 return (true); // PlugIn did something, and does not want the canvas to
7645 // do anything else
7646
7647 // Capture LeftUp's and time them, unless it already came from the timer.
7648
7649 // Detect end of chart dragging
7650 if (g_btouch && m_bChartDragging && event.LeftUp()) {
7651 StartChartDragInertia();
7652 }
7653
7654 if (b_handle_dclick && event.LeftUp() && !singleClickEventIsValid) {
7655 // Ignore the second LeftUp after the DClick.
7656 if (m_DoubleClickTimer->IsRunning()) {
7657 m_DoubleClickTimer->Stop();
7658 return (true);
7659 }
7660
7661 // Save the event for later running if there is no DClick.
7662 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7663 singleClickEvent = event;
7664 singleClickEventIsValid = true;
7665 return (true);
7666 }
7667
7668 // This logic is necessary on MSW to handle the case where
7669 // a context (right-click) menu is dismissed without action
7670 // by clicking on the chart surface.
7671 // We need to avoid an unintentional pan by eating some clicks...
7672#ifdef __WXMSW__
7673 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7674 if (g_click_stop > 0) {
7675 g_click_stop--;
7676 return (true);
7677 }
7678 }
7679#endif
7680
7681 // Kick off the Rotation control timer
7682 if (GetUpMode() == COURSE_UP_MODE) {
7683 m_b_rot_hidef = false;
7684 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7685 } else
7686 pRotDefTimer->Stop();
7687
7688 // Retrigger the route leg / AIS target popup timer
7689 bool bRoll = !g_btouch;
7690#ifdef __ANDROID__
7691 bRoll = g_bRollover;
7692#endif
7693 if (bRoll) {
7694 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7695 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7696 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7697 m_RolloverPopupTimer.Start(
7698 10,
7699 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7700 else
7701 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7702 }
7703
7704 // Retrigger the cursor tracking timer
7705 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7706
7707// Show cursor position on Status Bar, if present
7708// except for GTK, under which status bar updates are very slow
7709// due to Update() call.
7710// In this case, as a workaround, update the status window
7711// after an interval timer (pCurTrackTimer) pops, which will happen
7712// whenever the mouse has stopped moving for specified interval.
7713// See the method OnCursorTrackTimerEvent()
7714#if !defined(__WXGTK__) && !defined(__WXQT__)
7715 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7716#endif
7717
7718 // Send the current cursor lat/lon to all PlugIns requesting it
7719 if (g_pi_manager) {
7720 // Occasionally, MSW will produce nonsense events on right click....
7721 // This results in an error in cursor geo position, so we skip this case
7722 if ((x >= 0) && (y >= 0))
7723 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7724 }
7725
7726 if (!g_btouch) {
7727 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7728 wxPoint p = ClientToScreen(wxPoint(x, y));
7729 }
7730 }
7731
7732 if (1 ) {
7733 // Route Creation Rubber Banding
7734 if (m_routeState >= 2) {
7735 r_rband.x = x;
7736 r_rband.y = y;
7737 m_bDrawingRoute = true;
7738
7739 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7740 Refresh(false);
7741 }
7742
7743 // Measure Tool Rubber Banding
7744 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7745 r_rband.x = x;
7746 r_rband.y = y;
7747 m_bDrawingRoute = true;
7748
7749 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7750 Refresh(false);
7751 }
7752 }
7753 return bret;
7754}
7755
7756void ChartCanvas::CallPopupMenu(int x, int y) {
7757 int mx, my;
7758 mx = x;
7759 my = y;
7760
7761 last_drag.x = mx;
7762 last_drag.y = my;
7763 if (m_routeState) { // creating route?
7764 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
7765 return;
7766 }
7767 // General Right Click
7768 // Look for selectable objects
7769 double slat, slon;
7770 slat = m_cursor_lat;
7771 slon = m_cursor_lon;
7772
7773#if defined(__WXMAC__) || defined(__ANDROID__)
7774 wxScreenDC sdc;
7775 ocpnDC dc(sdc);
7776#else
7777 wxClientDC cdc(GetParent());
7778 ocpnDC dc(cdc);
7779#endif
7780
7781 SelectItem *pFindAIS;
7782 SelectItem *pFindRP;
7783 SelectItem *pFindRouteSeg;
7784 SelectItem *pFindTrackSeg;
7785 SelectItem *pFindCurrent = NULL;
7786 SelectItem *pFindTide = NULL;
7787
7788 // Deselect any current objects
7789 if (m_pSelectedRoute) {
7790 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7791 m_pSelectedRoute->DeSelectRoute();
7792#ifdef ocpnUSE_GL
7793 if (g_bopengl && m_glcc) {
7794 InvalidateGL();
7795 Update();
7796 } else
7797#endif
7798 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7799 }
7800
7801 if (m_pFoundRoutePoint) {
7802 m_pFoundRoutePoint->m_bPtIsSelected = false;
7803 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
7804 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
7805 }
7806
7809 if (g_btouch && m_pRoutePointEditTarget) {
7810 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7811 m_pRoutePointEditTarget->m_bPtIsSelected = false;
7812 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7813 }
7814
7815 // Get all the selectable things at the cursor
7816 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7817 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7818 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7819 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7820 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7821
7822 if (m_bShowCurrent)
7823 pFindCurrent =
7824 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7825
7826 if (m_bShowTide) // look for tide stations
7827 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7828
7829 int seltype = 0;
7830
7831 // Try for AIS targets first
7832 if (pFindAIS) {
7833 m_FoundAIS_MMSI = pFindAIS->GetUserData();
7834
7835 // Make sure the target data is available
7836 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
7837 seltype |= SELTYPE_AISTARGET;
7838 }
7839
7840 // Now the various Route Parts
7841
7842 m_pFoundRoutePoint = NULL;
7843 if (pFindRP) {
7844 RoutePoint *pFirstVizPoint = NULL;
7845 RoutePoint *pFoundActiveRoutePoint = NULL;
7846 RoutePoint *pFoundVizRoutePoint = NULL;
7847 Route *pSelectedActiveRoute = NULL;
7848 Route *pSelectedVizRoute = NULL;
7849
7850 // There is at least one routepoint, so get the whole list
7851 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7852 SelectableItemList SelList =
7853 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7854 wxSelectableItemListNode *node = SelList.GetFirst();
7855 while (node) {
7856 SelectItem *pFindSel = node->GetData();
7857
7858 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7859
7860 // Get an array of all routes using this point
7861 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7862
7863 // Use route array (if any) to determine actual visibility for this point
7864 bool brp_viz = false;
7865 if (proute_array) {
7866 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7867 Route *pr = (Route *)proute_array->Item(ir);
7868 if (pr->IsVisible()) {
7869 brp_viz = true;
7870 break;
7871 }
7872 }
7873 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7874 // but still exists as a waypoint
7875 brp_viz = prp->IsVisible(); // so treat as isolated point
7876
7877 } else
7878 brp_viz = prp->IsVisible(); // isolated point
7879
7880 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7881
7882 // Use route array to choose the appropriate route
7883 // Give preference to any active route, otherwise select the first visible
7884 // route in the array for this point
7885 m_pSelectedRoute = NULL;
7886 if (proute_array) {
7887 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7888 Route *pr = (Route *)proute_array->Item(ir);
7889 if (pr->m_bRtIsActive) {
7890 pSelectedActiveRoute = pr;
7891 pFoundActiveRoutePoint = prp;
7892 break;
7893 }
7894 }
7895
7896 if (NULL == pSelectedVizRoute) {
7897 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7898 Route *pr = (Route *)proute_array->Item(ir);
7899 if (pr->IsVisible()) {
7900 pSelectedVizRoute = pr;
7901 pFoundVizRoutePoint = prp;
7902 break;
7903 }
7904 }
7905 }
7906
7907 delete proute_array;
7908 }
7909
7910 node = node->GetNext();
7911 }
7912
7913 // Now choose the "best" selections
7914 if (pFoundActiveRoutePoint) {
7915 m_pFoundRoutePoint = pFoundActiveRoutePoint;
7916 m_pSelectedRoute = pSelectedActiveRoute;
7917 } else if (pFoundVizRoutePoint) {
7918 m_pFoundRoutePoint = pFoundVizRoutePoint;
7919 m_pSelectedRoute = pSelectedVizRoute;
7920 } else
7921 // default is first visible point in list
7922 m_pFoundRoutePoint = pFirstVizPoint;
7923
7924 if (m_pSelectedRoute) {
7925 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7926 } else if (m_pFoundRoutePoint)
7927 seltype |= SELTYPE_MARKPOINT;
7928
7929 // Highlite the selected point, to verify the proper right click
7930 // selection
7931 if (m_pFoundRoutePoint) {
7932 m_pFoundRoutePoint->m_bPtIsSelected = true;
7933 wxRect wp_rect;
7934 RoutePointGui(*m_pFoundRoutePoint)
7935 .CalculateDCRect(m_dc_route, this, &wp_rect);
7936 RefreshRect(wp_rect, true);
7937 }
7938 }
7939
7940 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7941 // routes But call the popup handler with identifier appropriate to the type
7942 if (pFindRouteSeg) // there is at least one select item
7943 {
7944 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7945 SelectableItemList SelList =
7946 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7947
7948 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
7949 {
7950 // Choose the first visible route containing segment in the list
7951 wxSelectableItemListNode *node = SelList.GetFirst();
7952 while (node) {
7953 SelectItem *pFindSel = node->GetData();
7954
7955 Route *pr = (Route *)pFindSel->m_pData3;
7956 if (pr->IsVisible()) {
7957 m_pSelectedRoute = pr;
7958 break;
7959 }
7960 node = node->GetNext();
7961 }
7962 }
7963
7964 if (m_pSelectedRoute) {
7965 if (NULL == m_pFoundRoutePoint)
7966 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7967
7968 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7969 if (m_pSelectedRoute->m_bRtIsSelected) {
7970#ifdef ocpnUSE_GL
7971 if (g_bopengl && m_glcc) {
7972 InvalidateGL();
7973 Update();
7974 } else
7975#endif
7976 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7977 }
7978
7979 seltype |= SELTYPE_ROUTESEGMENT;
7980 }
7981 }
7982
7983 if (pFindTrackSeg) {
7984 m_pSelectedTrack = NULL;
7985 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7986 SelectableItemList SelList =
7987 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7988
7989 // Choose the first visible track containing segment in the list
7990 wxSelectableItemListNode *node = SelList.GetFirst();
7991 while (node) {
7992 SelectItem *pFindSel = node->GetData();
7993
7994 Track *pt = (Track *)pFindSel->m_pData3;
7995 if (pt->IsVisible()) {
7996 m_pSelectedTrack = pt;
7997 break;
7998 }
7999 node = node->GetNext();
8000 }
8001
8002 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8003 }
8004
8005 bool bseltc = false;
8006 // if(0 == seltype)
8007 {
8008 if (pFindCurrent) {
8009 // There may be multiple current entries at the same point.
8010 // For example, there often is a current substation (with directions
8011 // specified) co-located with its master. We want to select the
8012 // substation, so that the direction will be properly indicated on the
8013 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8014 // substation)
8015 IDX_entry *pIDX_best_candidate;
8016
8017 SelectItem *pFind = NULL;
8018 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8019 SelectableItemList SelList = pSelectTC->FindSelectionList(
8020 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_CURRENTPOINT);
8021
8022 // Default is first entry
8023 wxSelectableItemListNode *node = SelList.GetFirst();
8024 pFind = node->GetData();
8025 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8026
8027 if (SelList.GetCount() > 1) {
8028 node = node->GetNext();
8029 while (node) {
8030 pFind = node->GetData();
8031 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8032 if (pIDX_candidate->IDX_type == 'c') {
8033 pIDX_best_candidate = pIDX_candidate;
8034 break;
8035 }
8036
8037 node = node->GetNext();
8038 } // while (node)
8039 } else {
8040 wxSelectableItemListNode *node = SelList.GetFirst();
8041 pFind = node->GetData();
8042 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8043 }
8044
8045 m_pIDXCandidate = pIDX_best_candidate;
8046
8047 if (0 == seltype) {
8048 DrawTCWindow(x, y, (void *)pIDX_best_candidate);
8049 Refresh(false);
8050 bseltc = true;
8051 } else
8052 seltype |= SELTYPE_CURRENTPOINT;
8053 }
8054
8055 else if (pFindTide) {
8056 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8057
8058 if (0 == seltype) {
8059 DrawTCWindow(x, y, (void *)pFindTide->m_pData1);
8060 Refresh(false);
8061 bseltc = true;
8062 } else
8063 seltype |= SELTYPE_TIDEPOINT;
8064 }
8065 }
8066
8067 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8068
8069 if (!bseltc) {
8070 InvokeCanvasMenu(x, y, seltype);
8071
8072 // Clean up if not deleted in InvokeCanvasMenu
8073 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8074 m_pSelectedRoute->m_bRtIsSelected = false;
8075 }
8076
8077 m_pSelectedRoute = NULL;
8078
8079 if (m_pFoundRoutePoint) {
8080 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8081 m_pFoundRoutePoint->m_bPtIsSelected = false;
8082 }
8083 m_pFoundRoutePoint = NULL;
8084
8085 Refresh(true);
8086 }
8087
8088 // Seth: Is this refresh needed?
8089 Refresh(false); // needed for MSW, not GTK Why??
8090}
8091bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8092 // For now just bail out completely if the point clicked is not on the chart
8093 if (std::isnan(m_cursor_lat)) return false;
8094
8095 // Mouse Clicks
8096 bool ret = false; // return true if processed
8097
8098 int x, y, mx, my;
8099 event.GetPosition(&x, &y);
8100 mx = x;
8101 my = y;
8102
8103 // Calculate meaningful SelectRadius
8104 float SelectRadius;
8105 SelectRadius = g_Platform->GetSelectRadiusPix() /
8106 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8107
8109 // We start with Double Click processing. The first left click just starts a
8110 // timer and is remembered, then we actually do something if there is a
8111 // LeftDClick. If there is, the two single clicks are ignored.
8112
8113 if (event.LeftDClick() && (cursor_region == CENTER)) {
8114 m_DoubleClickTimer->Start();
8115 singleClickEventIsValid = false;
8116
8117 double zlat, zlon;
8118 GetCanvasPixPoint(x * g_current_monitor_dip_px_ratio,
8119 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8120
8121 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8122 if (m_bShowAIS) {
8123 SelectItem *pFindAIS;
8124 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8125
8126 if (pFindAIS) {
8127 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8128 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8129 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8130 }
8131 return true;
8132 }
8133 }
8134
8135 SelectableItemList rpSelList =
8136 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8137 wxSelectableItemListNode *node = rpSelList.GetFirst();
8138 bool b_onRPtarget = false;
8139 while (node) {
8140 SelectItem *pFind = node->GetData();
8141 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8142 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8143 b_onRPtarget = true;
8144 break;
8145 }
8146 node = node->GetNext();
8147 }
8148
8149 // Double tap with selected RoutePoint or Mark
8150
8151 if (m_pRoutePointEditTarget) {
8152 if (b_onRPtarget) {
8153 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8154 return true;
8155 } else {
8156 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8157 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8158 if (g_btouch)
8159 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8160 wxRect wp_rect;
8161 RoutePointGui(*m_pRoutePointEditTarget)
8162 .CalculateDCRect(m_dc_route, this, &wp_rect);
8163 m_pRoutePointEditTarget = NULL; // cancel selection
8164 RefreshRect(wp_rect, true);
8165 return true;
8166 }
8167 } else {
8168 node = rpSelList.GetFirst();
8169 if (node) {
8170 SelectItem *pFind = node->GetData();
8171 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8172 if (frp) {
8173 wxArrayPtrVoid *proute_array =
8174 g_pRouteMan->GetRouteArrayContaining(frp);
8175
8176 // Use route array (if any) to determine actual visibility for this
8177 // point
8178 bool brp_viz = false;
8179 if (proute_array) {
8180 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8181 Route *pr = (Route *)proute_array->Item(ir);
8182 if (pr->IsVisible()) {
8183 brp_viz = true;
8184 break;
8185 }
8186 }
8187 delete proute_array;
8188 if (!brp_viz &&
8189 frp->IsShared()) // is not visible as part of route, but still
8190 // exists as a waypoint
8191 brp_viz = frp->IsVisible(); // so treat as isolated point
8192 } else
8193 brp_viz = frp->IsVisible(); // isolated point
8194
8195 if (brp_viz) {
8196 ShowMarkPropertiesDialog(frp);
8197 return true;
8198 }
8199 }
8200 }
8201 }
8202
8203 SelectItem *cursorItem;
8204 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8205
8206 if (cursorItem) {
8207 Route *pr = (Route *)cursorItem->m_pData3;
8208 if (pr->IsVisible()) {
8209 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8210 return true;
8211 }
8212 }
8213
8214 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8215
8216 if (cursorItem) {
8217 Track *pt = (Track *)cursorItem->m_pData3;
8218 if (pt->IsVisible()) {
8219 ShowTrackPropertiesDialog(pt);
8220 return true;
8221 }
8222 }
8223
8224 // Found no object to act on, so show chart info.
8225
8226 ShowObjectQueryWindow(x, y, zlat, zlon);
8227 return true;
8228 }
8229
8231 if (event.LeftDown()) {
8232 // This really should not be needed, but....
8233 // on Windows, when using wxAUIManager, sometimes the focus is lost
8234 // when clicking into another pane, e.g.the AIS target list, and then back
8235 // to this pane. Oddly, some mouse events are not lost, however. Like this
8236 // one....
8237 SetFocus();
8238
8239 last_drag.x = mx;
8240 last_drag.y = my;
8241 leftIsDown = true;
8242
8243 if (!g_btouch) {
8244 if (m_routeState) // creating route?
8245 {
8246 double rlat, rlon;
8247 bool appending = false;
8248 bool inserting = false;
8249 Route *tail = 0;
8250
8251 SetCursor(*pCursorPencil);
8252 rlat = m_cursor_lat;
8253 rlon = m_cursor_lon;
8254
8255 m_bRouteEditing = true;
8256
8257 if (m_routeState == 1) {
8258 m_pMouseRoute = new Route();
8259 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8260 pRouteList->Append(m_pMouseRoute);
8261 r_rband.x = x;
8262 r_rband.y = y;
8263 }
8264
8265 // Check to see if there is a nearby point which may be reused
8266 RoutePoint *pMousePoint = NULL;
8267
8268 // Calculate meaningful SelectRadius
8269 double nearby_radius_meters =
8270 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8271
8272 RoutePoint *pNearbyPoint =
8273 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8274 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8275 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8276 wxArrayPtrVoid *proute_array =
8277 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8278
8279 // Use route array (if any) to determine actual visibility for this
8280 // point
8281 bool brp_viz = false;
8282 if (proute_array) {
8283 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8284 Route *pr = (Route *)proute_array->Item(ir);
8285 if (pr->IsVisible()) {
8286 brp_viz = true;
8287 break;
8288 }
8289 }
8290 delete proute_array;
8291 if (!brp_viz &&
8292 pNearbyPoint->IsShared()) // is not visible as part of route,
8293 // but still exists as a waypoint
8294 brp_viz =
8295 pNearbyPoint->IsVisible(); // so treat as isolated point
8296 } else
8297 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8298
8299 if (brp_viz) {
8300 wxString msg = _("Use nearby waypoint?");
8301 // Don't add a mark without name to the route. Name it if needed
8302 const bool noname(pNearbyPoint->GetName() == "");
8303 if (noname) {
8304 msg =
8305 _("Use nearby nameless waypoint and name it M with"
8306 " a unique number?");
8307 }
8308 // Avoid route finish on focus change for message dialog
8309 m_FinishRouteOnKillFocus = false;
8310 int dlg_return =
8311 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8312 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8313 m_FinishRouteOnKillFocus = true;
8314 if (dlg_return == wxID_YES) {
8315 if (noname) {
8316 if (m_pMouseRoute) {
8317 int last_wp_num = m_pMouseRoute->GetnPoints();
8318 // AP-ECRMB will truncate to 6 characters
8319 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8320 wxString wp_name = wxString::Format(
8321 "M%002i-%s", last_wp_num + 1, guid_short);
8322 pNearbyPoint->SetName(wp_name);
8323 } else
8324 pNearbyPoint->SetName("WPXX");
8325 }
8326 pMousePoint = pNearbyPoint;
8327
8328 // Using existing waypoint, so nothing to delete for undo.
8329 if (m_routeState > 1)
8330 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8331 Undo_HasParent, NULL);
8332
8333 tail =
8334 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8335 bool procede = false;
8336 if (tail) {
8337 procede = true;
8338 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8339 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8340 procede = false;
8341 }
8342
8343 if (procede) {
8344 int dlg_return;
8345 m_FinishRouteOnKillFocus = false;
8346 if (m_routeState ==
8347 1) { // first point in new route, preceeding route to be
8348 // added? Not touch case
8349
8350 wxString dmsg =
8351 _("Insert first part of this route in the new route?");
8352 if (tail->GetIndexOf(pMousePoint) ==
8353 tail->GetnPoints()) // Starting on last point of another
8354 // route?
8355 dmsg = _("Insert this route in the new route?");
8356
8357 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8358 dlg_return = OCPNMessageBox(
8359 this, dmsg, _("OpenCPN Route Create"),
8360 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8361 m_FinishRouteOnKillFocus = true;
8362
8363 if (dlg_return == wxID_YES) {
8364 inserting = true; // part of the other route will be
8365 // preceeding the new route
8366 }
8367 }
8368 } else {
8369 wxString dmsg =
8370 _("Append last part of this route to the new route?");
8371 if (tail->GetIndexOf(pMousePoint) == 1)
8372 dmsg = _(
8373 "Append this route to the new route?"); // Picking the
8374 // first point
8375 // of another
8376 // route?
8377
8378 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8379 dlg_return = OCPNMessageBox(
8380 this, dmsg, _("OpenCPN Route Create"),
8381 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8382 m_FinishRouteOnKillFocus = true;
8383
8384 if (dlg_return == wxID_YES) {
8385 appending = true; // part of the other route will be
8386 // appended to the new route
8387 }
8388 }
8389 }
8390 }
8391
8392 // check all other routes to see if this point appears in any
8393 // other route If it appears in NO other route, then it should e
8394 // considered an isolated mark
8395 if (!FindRouteContainingWaypoint(pMousePoint))
8396 pMousePoint->SetShared(true);
8397 }
8398 }
8399 }
8400
8401 if (NULL == pMousePoint) { // need a new point
8402 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8403 _T(""), wxEmptyString);
8404 pMousePoint->SetNameShown(false);
8405
8406 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8407
8408 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8409
8410 if (m_routeState > 1)
8411 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8412 Undo_IsOrphanded, NULL);
8413 }
8414
8415 if (m_pMouseRoute) {
8416 if (m_routeState == 1) {
8417 // First point in the route.
8418 m_pMouseRoute->AddPoint(pMousePoint);
8419 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8420 } else {
8421 if (m_pMouseRoute->m_NextLegGreatCircle) {
8422 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8423 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8424 &rhumbBearing, &rhumbDist);
8425 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8426 rlat, &gcDist, &gcBearing, NULL);
8427 double gcDistNM = gcDist / 1852.0;
8428
8429 // Empirically found expression to get reasonable route segments.
8430 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8431 pow(rhumbDist - gcDistNM - 1, 0.5);
8432
8433 wxString msg;
8434 msg << _("For this leg the Great Circle route is ")
8435 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8436 << _(" shorter than rhumbline.\n\n")
8437 << _("Would you like include the Great Circle routing points "
8438 "for this leg?");
8439
8440 m_FinishRouteOnKillFocus = false;
8441 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8442 // does not fully capture mouse
8443
8444 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8445 wxYES_NO | wxNO_DEFAULT);
8446
8447 m_disable_edge_pan = false;
8448 m_FinishRouteOnKillFocus = true;
8449
8450 if (answer == wxID_YES) {
8451 RoutePoint *gcPoint;
8452 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8453 wxRealPoint gcCoord;
8454
8455 for (int i = 1; i <= segmentCount; i++) {
8456 double fraction = (double)i * (1.0 / (double)segmentCount);
8457 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8458 gcDist * fraction, gcBearing,
8459 &gcCoord.x, &gcCoord.y, NULL);
8460
8461 if (i < segmentCount) {
8462 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
8463 _T(""), wxEmptyString);
8464 gcPoint->SetNameShown(false);
8465 // pConfig->AddNewWayPoint(gcPoint, -1);
8466 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8467
8468 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8469 gcPoint);
8470 } else {
8471 gcPoint = pMousePoint; // Last point, previously exsisting!
8472 }
8473
8474 m_pMouseRoute->AddPoint(gcPoint);
8475 pSelect->AddSelectableRouteSegment(
8476 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8477 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8478 prevGcPoint = gcPoint;
8479 }
8480
8481 undo->CancelUndoableAction(true);
8482
8483 } else {
8484 m_pMouseRoute->AddPoint(pMousePoint);
8485 pSelect->AddSelectableRouteSegment(
8486 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8487 pMousePoint, m_pMouseRoute);
8488 undo->AfterUndoableAction(m_pMouseRoute);
8489 }
8490 } else {
8491 // Ordinary rhumblinesegment.
8492 m_pMouseRoute->AddPoint(pMousePoint);
8493 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8494 rlon, m_prev_pMousePoint,
8495 pMousePoint, m_pMouseRoute);
8496 undo->AfterUndoableAction(m_pMouseRoute);
8497 }
8498 }
8499 }
8500 m_prev_rlat = rlat;
8501 m_prev_rlon = rlon;
8502 m_prev_pMousePoint = pMousePoint;
8503 if (m_pMouseRoute)
8504 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8505
8506 m_routeState++;
8507
8508 if (appending ||
8509 inserting) { // Appending a route or making a new route
8510 int connect = tail->GetIndexOf(pMousePoint);
8511 if (connect == 1) {
8512 inserting = false; // there is nothing to insert
8513 appending = true; // so append
8514 }
8515 int length = tail->GetnPoints();
8516
8517 int i;
8518 int start, stop;
8519 if (appending) {
8520 start = connect + 1;
8521 stop = length;
8522 } else { // inserting
8523 start = 1;
8524 stop = connect;
8525 m_pMouseRoute->RemovePoint(
8526 m_pMouseRoute
8527 ->GetLastPoint()); // Remove the first and only point
8528 }
8529 for (i = start; i <= stop; i++) {
8530 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8531 if (m_pMouseRoute)
8532 m_pMouseRoute->m_lastMousePointIndex =
8533 m_pMouseRoute->GetnPoints();
8534 m_routeState++;
8535 gFrame->RefreshAllCanvas();
8536 ret = true;
8537 }
8538 m_prev_rlat =
8539 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8540 m_prev_rlon =
8541 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8542 m_pMouseRoute->FinalizeForRendering();
8543 }
8544 gFrame->RefreshAllCanvas();
8545 ret = true;
8546 }
8547
8548 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8549 {
8550 SetCursor(*pCursorPencil);
8551
8552 if (!m_pMeasureRoute) {
8553 m_pMeasureRoute = new Route();
8554 pRouteList->Append(m_pMeasureRoute);
8555 }
8556
8557 if (m_nMeasureState == 1) {
8558 r_rband.x = x;
8559 r_rband.y = y;
8560 }
8561
8562 RoutePoint *pMousePoint = new RoutePoint(m_cursor_lat, m_cursor_lon,
8563 wxString(_T ( "circle" )),
8564 wxEmptyString, wxEmptyString);
8565 pMousePoint->m_bShowName = false;
8566 pMousePoint->SetShowWaypointRangeRings(false);
8567
8568 m_pMeasureRoute->AddPoint(pMousePoint);
8569
8570 m_prev_rlat = m_cursor_lat;
8571 m_prev_rlon = m_cursor_lon;
8572 m_prev_pMousePoint = pMousePoint;
8573 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8574
8575 m_nMeasureState++;
8576 gFrame->RefreshAllCanvas();
8577 ret = true;
8578 }
8579
8580 else {
8581 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8582 }
8583 } // !g_btouch
8584 else { // g_btouch
8585
8586 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8587 // if near screen edge, pan with injection
8588 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8589 // return;
8590 // }
8591 }
8592 }
8593
8594 if (ret) return true;
8595 }
8596
8597 if (event.Dragging()) {
8598 // in touch screen mode ensure the finger/cursor is on the selected point's
8599 // radius to allow dragging
8600 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8601 if (g_btouch) {
8602 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8603 SelectItem *pFind = NULL;
8604 SelectableItemList SelList = pSelect->FindSelectionList(
8605 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8606 wxSelectableItemListNode *node = SelList.GetFirst();
8607 while (node) {
8608 pFind = node->GetData();
8609 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8610 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8611 node = node->GetNext();
8612 }
8613 }
8614
8615 // Check for use of dragHandle
8616 if (m_pRoutePointEditTarget &&
8617 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8618 SelectItem *pFind = NULL;
8619 SelectableItemList SelList = pSelect->FindSelectionList(
8620 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8621 wxSelectableItemListNode *node = SelList.GetFirst();
8622 while (node) {
8623 pFind = node->GetData();
8624 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8625 if (m_pRoutePointEditTarget == frp) {
8626 m_bIsInRadius = true;
8627 break;
8628 }
8629 node = node->GetNext();
8630 }
8631
8632 if (!m_dragoffsetSet) {
8633 RoutePointGui(*m_pRoutePointEditTarget)
8634 .PresetDragOffset(this, mouse_x, mouse_y);
8635 m_dragoffsetSet = true;
8636 }
8637 }
8638 }
8639
8640 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8641 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8642
8643 if (NULL == g_pMarkInfoDialog) {
8644 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8645 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8646 DraggingAllowed = false;
8647
8648 if (m_pRoutePointEditTarget &&
8649 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8650 DraggingAllowed = false;
8651
8652 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8653
8654 if (DraggingAllowed) {
8655 if (!undo->InUndoableAction()) {
8656 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8657 Undo_NeedsCopy, m_pFoundPoint);
8658 }
8659
8660 // Get the update rectangle for the union of the un-edited routes
8661 wxRect pre_rect;
8662
8663 if (!g_bopengl && m_pEditRouteArray) {
8664 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8665 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8666 // Need to validate route pointer
8667 // Route may be gone due to drgging close to ownship with
8668 // "Delete On Arrival" state set, as in the case of
8669 // navigating to an isolated waypoint on a temporary route
8670 if (g_pRouteMan->IsRouteValid(pr)) {
8671 wxRect route_rect;
8672 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8673 pre_rect.Union(route_rect);
8674 }
8675 }
8676 }
8677
8678 double new_cursor_lat = m_cursor_lat;
8679 double new_cursor_lon = m_cursor_lon;
8680
8681 if (CheckEdgePan(x, y, true, 5, 2))
8682 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8683
8684 // update the point itself
8685 if (g_btouch) {
8686 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8687 // new_cursor_lat, new_cursor_lon);
8688 RoutePointGui(*m_pRoutePointEditTarget)
8689 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8690 // update the Drag Handle entry in the pSelect list
8691 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8692 m_pRoutePointEditTarget,
8693 SELTYPE_DRAGHANDLE);
8694 m_pFoundPoint->m_slat =
8695 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8696 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8697 } else {
8698 m_pRoutePointEditTarget->m_lat =
8699 new_cursor_lat; // update the RoutePoint entry
8700 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8701 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8702 m_pFoundPoint->m_slat =
8703 new_cursor_lat; // update the SelectList entry
8704 m_pFoundPoint->m_slon = new_cursor_lon;
8705 }
8706
8707 // Update the MarkProperties Dialog, if currently shown
8708 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8709 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8710 g_pMarkInfoDialog->UpdateProperties(true);
8711 }
8712
8713 if (g_bopengl) {
8714 // InvalidateGL();
8715 Refresh(false);
8716 } else {
8717 // Get the update rectangle for the edited route
8718 wxRect post_rect;
8719
8720 if (m_pEditRouteArray) {
8721 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8722 ir++) {
8723 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8724 if (g_pRouteMan->IsRouteValid(pr)) {
8725 wxRect route_rect;
8726 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8727 post_rect.Union(route_rect);
8728 }
8729 }
8730 }
8731
8732 // Invalidate the union region
8733 pre_rect.Union(post_rect);
8734 RefreshRect(pre_rect, false);
8735 }
8736 gFrame->RefreshCanvasOther(this);
8737 m_bRoutePoinDragging = true;
8738 }
8739 ret = true;
8740 } // if Route Editing
8741
8742 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8743 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8744
8745 if (NULL == g_pMarkInfoDialog) {
8746 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8747 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8748 DraggingAllowed = false;
8749
8750 if (m_pRoutePointEditTarget &&
8751 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8752 DraggingAllowed = false;
8753
8754 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8755
8756 if (DraggingAllowed) {
8757 if (!undo->InUndoableAction()) {
8758 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8759 Undo_NeedsCopy, m_pFoundPoint);
8760 }
8761
8762 // The mark may be an anchorwatch
8763 double lpp1 = 0.;
8764 double lpp2 = 0.;
8765 double lppmax;
8766
8767 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8768 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8769 }
8770 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8771 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8772 }
8773 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8774
8775 // Get the update rectangle for the un-edited mark
8776 wxRect pre_rect;
8777 if (!g_bopengl) {
8778 RoutePointGui(*m_pRoutePointEditTarget)
8779 .CalculateDCRect(m_dc_route, this, &pre_rect);
8780 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8781 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8782 (int)(lppmax - (pre_rect.height / 2)));
8783 }
8784
8785 // update the point itself
8786 if (g_btouch) {
8787 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8788 // m_cursor_lat, m_cursor_lon);
8789 RoutePointGui(*m_pRoutePointEditTarget)
8790 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8791 // update the Drag Handle entry in the pSelect list
8792 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
8793 m_pRoutePointEditTarget,
8794 SELTYPE_DRAGHANDLE);
8795 m_pFoundPoint->m_slat =
8796 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8797 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8798 } else {
8799 m_pRoutePointEditTarget->m_lat =
8800 m_cursor_lat; // update the RoutePoint entry
8801 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
8802 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8803 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
8804 m_pFoundPoint->m_slon = m_cursor_lon;
8805 }
8806
8807 // Update the MarkProperties Dialog, if currently shown
8808 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8809 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8810 g_pMarkInfoDialog->UpdateProperties(true);
8811 }
8812
8813 // Invalidate the union region
8814 if (g_bopengl) {
8815 if (!g_btouch) InvalidateGL();
8816 Refresh(false);
8817 } else {
8818 // Get the update rectangle for the edited mark
8819 wxRect post_rect;
8820 RoutePointGui(*m_pRoutePointEditTarget)
8821 .CalculateDCRect(m_dc_route, this, &post_rect);
8822 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
8823 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
8824 (int)(lppmax - (post_rect.height / 2)));
8825
8826 // Invalidate the union region
8827 pre_rect.Union(post_rect);
8828 RefreshRect(pre_rect, false);
8829 }
8830 gFrame->RefreshCanvasOther(this);
8831 m_bRoutePoinDragging = true;
8832 }
8833 ret = true;
8834 }
8835
8836 if (ret) return true;
8837 } // dragging
8838
8839 if (event.LeftUp()) {
8840 bool b_startedit_route = false;
8841 m_dragoffsetSet = false;
8842
8843 if (g_btouch) {
8844 m_bChartDragging = false;
8845 m_bIsInRadius = false;
8846
8847 if (m_routeState) // creating route?
8848 {
8849 if (m_bedge_pan) {
8850 m_bedge_pan = false;
8851 return false;
8852 }
8853
8854 double rlat, rlon;
8855 bool appending = false;
8856 bool inserting = false;
8857 Route *tail = 0;
8858
8859 rlat = m_cursor_lat;
8860 rlon = m_cursor_lon;
8861
8862 if (m_pRoutePointEditTarget) {
8863 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8864 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8865 if (!g_bopengl) {
8866 wxRect wp_rect;
8867 RoutePointGui(*m_pRoutePointEditTarget)
8868 .CalculateDCRect(m_dc_route, this, &wp_rect);
8869 RefreshRect(wp_rect, true);
8870 }
8871 m_pRoutePointEditTarget = NULL;
8872 }
8873 m_bRouteEditing = true;
8874
8875 if (m_routeState == 1) {
8876 m_pMouseRoute = new Route();
8877 m_pMouseRoute->SetHiLite(50);
8878 pRouteList->Append(m_pMouseRoute);
8879 r_rband.x = x;
8880 r_rband.y = y;
8881 }
8882
8883 // Check to see if there is a nearby point which may be reused
8884 RoutePoint *pMousePoint = NULL;
8885
8886 // Calculate meaningful SelectRadius
8887 double nearby_radius_meters =
8888 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8889
8890 RoutePoint *pNearbyPoint =
8891 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8892 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8893 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8894 int dlg_return;
8895#ifndef __WXOSX__
8896 m_FinishRouteOnKillFocus =
8897 false; // Avoid route finish on focus change for message dialog
8898 dlg_return = OCPNMessageBox(
8899 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
8900 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8901 m_FinishRouteOnKillFocus = true;
8902#else
8903 dlg_return = wxID_YES;
8904#endif
8905 if (dlg_return == wxID_YES) {
8906 pMousePoint = pNearbyPoint;
8907
8908 // Using existing waypoint, so nothing to delete for undo.
8909 if (m_routeState > 1)
8910 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8911 Undo_HasParent, NULL);
8912 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8913
8914 bool procede = false;
8915 if (tail) {
8916 procede = true;
8917 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8918 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8919 procede = false;
8920 }
8921
8922 if (procede) {
8923 int dlg_return;
8924 m_FinishRouteOnKillFocus = false;
8925 if (m_routeState == 1) { // first point in new route, preceeding
8926 // route to be added? touch case
8927
8928 wxString dmsg =
8929 _("Insert first part of this route in the new route?");
8930 if (tail->GetIndexOf(pMousePoint) ==
8931 tail->GetnPoints()) // Starting on last point of another
8932 // route?
8933 dmsg = _("Insert this route in the new route?");
8934
8935 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8936 dlg_return =
8937 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8938 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8939 m_FinishRouteOnKillFocus = true;
8940
8941 if (dlg_return == wxID_YES) {
8942 inserting = true; // part of the other route will be
8943 // preceeding the new route
8944 }
8945 }
8946 } else {
8947 wxString dmsg =
8948 _("Append last part of this route to the new route?");
8949 if (tail->GetIndexOf(pMousePoint) == 1)
8950 dmsg = _(
8951 "Append this route to the new route?"); // Picking the
8952 // first point of
8953 // another route?
8954
8955 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8956 dlg_return =
8957 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8958 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8959 m_FinishRouteOnKillFocus = true;
8960
8961 if (dlg_return == wxID_YES) {
8962 appending = true; // part of the other route will be
8963 // appended to the new route
8964 }
8965 }
8966 }
8967 }
8968
8969 // check all other routes to see if this point appears in any other
8970 // route If it appears in NO other route, then it should e
8971 // considered an isolated mark
8972 if (!FindRouteContainingWaypoint(pMousePoint))
8973 pMousePoint->SetShared(true);
8974 }
8975 }
8976
8977 if (NULL == pMousePoint) { // need a new point
8978 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8979 _T(""), wxEmptyString);
8980 pMousePoint->SetNameShown(false);
8981
8982 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8983 NavObj_dB::GetInstance().InsertRoutePoint(pMousePoint);
8984
8985 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8986
8987 if (m_routeState > 1)
8988 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8989 Undo_IsOrphanded, NULL);
8990 }
8991
8992 if (m_routeState == 1) {
8993 // First point in the route.
8994 m_pMouseRoute->AddPoint(pMousePoint);
8995 } else {
8996 if (m_pMouseRoute->m_NextLegGreatCircle) {
8997 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8998 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8999 &rhumbBearing, &rhumbDist);
9000 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9001 &gcDist, &gcBearing, NULL);
9002 double gcDistNM = gcDist / 1852.0;
9003
9004 // Empirically found expression to get reasonable route segments.
9005 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9006 pow(rhumbDist - gcDistNM - 1, 0.5);
9007
9008 wxString msg;
9009 msg << _("For this leg the Great Circle route is ")
9010 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9011 << _(" shorter than rhumbline.\n\n")
9012 << _("Would you like include the Great Circle routing points "
9013 "for this leg?");
9014
9015#ifndef __WXOSX__
9016 m_FinishRouteOnKillFocus = false;
9017 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9018 wxYES_NO | wxNO_DEFAULT);
9019 m_FinishRouteOnKillFocus = true;
9020#else
9021 int answer = wxID_NO;
9022#endif
9023
9024 if (answer == wxID_YES) {
9025 RoutePoint *gcPoint;
9026 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9027 wxRealPoint gcCoord;
9028
9029 for (int i = 1; i <= segmentCount; i++) {
9030 double fraction = (double)i * (1.0 / (double)segmentCount);
9031 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9032 gcDist * fraction, gcBearing,
9033 &gcCoord.x, &gcCoord.y, NULL);
9034
9035 if (i < segmentCount) {
9036 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
9037 _T(""), wxEmptyString);
9038 gcPoint->SetNameShown(false);
9039 // pConfig->AddNewWayPoint(gcPoint, -1);
9040 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
9041
9042 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9043 gcPoint);
9044 } else {
9045 gcPoint = pMousePoint; // Last point, previously exsisting!
9046 }
9047
9048 m_pMouseRoute->AddPoint(gcPoint);
9049 pSelect->AddSelectableRouteSegment(
9050 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9051 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9052 prevGcPoint = gcPoint;
9053 }
9054
9055 undo->CancelUndoableAction(true);
9056
9057 } else {
9058 m_pMouseRoute->AddPoint(pMousePoint);
9059 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9060 rlon, m_prev_pMousePoint,
9061 pMousePoint, m_pMouseRoute);
9062 undo->AfterUndoableAction(m_pMouseRoute);
9063 }
9064 } else {
9065 // Ordinary rhumblinesegment.
9066 m_pMouseRoute->AddPoint(pMousePoint);
9067 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9068 rlon, m_prev_pMousePoint,
9069 pMousePoint, m_pMouseRoute);
9070 undo->AfterUndoableAction(m_pMouseRoute);
9071 }
9072 }
9073
9074 m_prev_rlat = rlat;
9075 m_prev_rlon = rlon;
9076 m_prev_pMousePoint = pMousePoint;
9077 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9078
9079 m_routeState++;
9080
9081 if (appending ||
9082 inserting) { // Appending a route or making a new route
9083 int connect = tail->GetIndexOf(pMousePoint);
9084 if (connect == 1) {
9085 inserting = false; // there is nothing to insert
9086 appending = true; // so append
9087 }
9088 int length = tail->GetnPoints();
9089
9090 int i;
9091 int start, stop;
9092 if (appending) {
9093 start = connect + 1;
9094 stop = length;
9095 } else { // inserting
9096 start = 1;
9097 stop = connect;
9098 m_pMouseRoute->RemovePoint(
9099 m_pMouseRoute
9100 ->GetLastPoint()); // Remove the first and only point
9101 }
9102 for (i = start; i <= stop; i++) {
9103 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9104 if (m_pMouseRoute)
9105 m_pMouseRoute->m_lastMousePointIndex =
9106 m_pMouseRoute->GetnPoints();
9107 m_routeState++;
9108 gFrame->RefreshAllCanvas();
9109 ret = true;
9110 }
9111 m_prev_rlat =
9112 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9113 m_prev_rlon =
9114 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9115 m_pMouseRoute->FinalizeForRendering();
9116 }
9117
9118 Refresh(true);
9119 ret = true;
9120 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9121 {
9122 if (m_bedge_pan) {
9123 m_bedge_pan = false;
9124 return false;
9125 }
9126
9127 if (m_nMeasureState == 1) {
9128 m_pMeasureRoute = new Route();
9129 pRouteList->Append(m_pMeasureRoute);
9130 r_rband.x = x;
9131 r_rband.y = y;
9132 }
9133
9134 if (m_pMeasureRoute) {
9135 RoutePoint *pMousePoint = new RoutePoint(
9136 m_cursor_lat, m_cursor_lon, wxString(_T ( "circle" )),
9137 wxEmptyString, wxEmptyString);
9138 pMousePoint->m_bShowName = false;
9139
9140 m_pMeasureRoute->AddPoint(pMousePoint);
9141
9142 m_prev_rlat = m_cursor_lat;
9143 m_prev_rlon = m_cursor_lon;
9144 m_prev_pMousePoint = pMousePoint;
9145 m_pMeasureRoute->m_lastMousePointIndex =
9146 m_pMeasureRoute->GetnPoints();
9147
9148 m_nMeasureState++;
9149 } else {
9150 CancelMeasureRoute();
9151 }
9152
9153 Refresh(true);
9154 ret = true;
9155 } else {
9156 bool bSelectAllowed = true;
9157 if (NULL == g_pMarkInfoDialog) {
9158 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9159 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9160 bSelectAllowed = false;
9161
9162 /*if this left up happens at the end of a route point dragging and if
9163 the cursor/thumb is on the draghandle icon, not on the point iself a new
9164 selection will select nothing and the drag will never be ended, so the
9165 legs around this point never selectable. At this step we don't need a
9166 new selection, just keep the previoulsly selected and dragged point */
9167 if (m_bRoutePoinDragging) bSelectAllowed = false;
9168
9169 if (bSelectAllowed) {
9170 bool b_was_editing_mark = m_bMarkEditing;
9171 bool b_was_editing_route = m_bRouteEditing;
9172 FindRoutePointsAtCursor(SelectRadius,
9173 true); // Possibly selecting a point in a
9174 // route for later dragging
9175
9176 /*route and a mark points in layer can't be dragged so should't be
9177 * selected and no draghandle icon*/
9178 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9179 m_pRoutePointEditTarget = NULL;
9180
9181 if (!b_was_editing_route) {
9182 if (m_pEditRouteArray) {
9183 b_startedit_route = true;
9184
9185 // Hide the track and route rollover during route point edit, not
9186 // needed, and may be confusing
9187 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9188 m_pTrackRolloverWin->IsActive(false);
9189 }
9190 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9191 m_pRouteRolloverWin->IsActive(false);
9192 }
9193
9194 wxRect pre_rect;
9195 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9196 ir++) {
9197 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9198 // Need to validate route pointer
9199 // Route may be gone due to drgging close to ownship with
9200 // "Delete On Arrival" state set, as in the case of
9201 // navigating to an isolated waypoint on a temporary route
9202 if (g_pRouteMan->IsRouteValid(pr)) {
9203 // pr->SetHiLite(50);
9204 wxRect route_rect;
9205 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9206 pre_rect.Union(route_rect);
9207 }
9208 }
9209 RefreshRect(pre_rect, true);
9210 }
9211 } else {
9212 b_startedit_route = false;
9213 }
9214
9215 // Mark editing
9216 if (m_pRoutePointEditTarget) {
9217 if (b_was_editing_mark ||
9218 b_was_editing_route) { // kill previous hilight
9219 if (m_lastRoutePointEditTarget) {
9220 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9221 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9222 RoutePointGui(*m_lastRoutePointEditTarget)
9223 .EnableDragHandle(false);
9224 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9225 SELTYPE_DRAGHANDLE);
9226 }
9227 }
9228
9229 if (m_pRoutePointEditTarget) {
9230 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9231 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9232 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9233 wxPoint2DDouble dragHandlePoint =
9234 RoutePointGui(*m_pRoutePointEditTarget)
9235 .GetDragHandlePoint(this);
9236 pSelect->AddSelectablePoint(
9237 dragHandlePoint.m_y, dragHandlePoint.m_x,
9238 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9239 }
9240 } else { // Deselect everything
9241 if (m_lastRoutePointEditTarget) {
9242 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9243 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9244 RoutePointGui(*m_lastRoutePointEditTarget)
9245 .EnableDragHandle(false);
9246 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9247 SELTYPE_DRAGHANDLE);
9248
9249 // Clear any routes being edited, probably orphans
9250 wxArrayPtrVoid *lastEditRouteArray =
9251 g_pRouteMan->GetRouteArrayContaining(
9252 m_lastRoutePointEditTarget);
9253 if (lastEditRouteArray) {
9254 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9255 ir++) {
9256 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9257 if (g_pRouteMan->IsRouteValid(pr)) {
9258 pr->m_bIsBeingEdited = false;
9259 }
9260 }
9261 delete lastEditRouteArray;
9262 }
9263 }
9264 }
9265
9266 // Do the refresh
9267
9268 if (g_bopengl) {
9269 InvalidateGL();
9270 Refresh(false);
9271 } else {
9272 if (m_lastRoutePointEditTarget) {
9273 wxRect wp_rect;
9274 RoutePointGui(*m_lastRoutePointEditTarget)
9275 .CalculateDCRect(m_dc_route, this, &wp_rect);
9276 RefreshRect(wp_rect, true);
9277 }
9278
9279 if (m_pRoutePointEditTarget) {
9280 wxRect wp_rect;
9281 RoutePointGui(*m_pRoutePointEditTarget)
9282 .CalculateDCRect(m_dc_route, this, &wp_rect);
9283 RefreshRect(wp_rect, true);
9284 }
9285 }
9286 }
9287 } // bSelectAllowed
9288
9289 // Check to see if there is a route or AIS target under the cursor
9290 // If so, start the rollover timer which creates the popup
9291 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9292 bool b_start_rollover = false;
9293 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9294 SelectItem *pFind = pSelectAIS->FindSelection(
9295 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9296 if (pFind) b_start_rollover = true;
9297 }
9298
9299 if (!b_start_rollover && !b_startedit_route) {
9300 SelectableItemList SelList = pSelect->FindSelectionList(
9301 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9302 wxSelectableItemListNode *node = SelList.GetFirst();
9303 while (node) {
9304 SelectItem *pFindSel = node->GetData();
9305
9306 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9307
9308 if (pr && pr->IsVisible()) {
9309 b_start_rollover = true;
9310 break;
9311 }
9312 node = node->GetNext();
9313 } // while
9314 }
9315
9316 if (!b_start_rollover && !b_startedit_route) {
9317 SelectableItemList SelList = pSelect->FindSelectionList(
9318 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9319 wxSelectableItemListNode *node = SelList.GetFirst();
9320 while (node) {
9321 SelectItem *pFindSel = node->GetData();
9322
9323 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9324
9325 if (tr && tr->IsVisible()) {
9326 b_start_rollover = true;
9327 break;
9328 }
9329 node = node->GetNext();
9330 } // while
9331 }
9332
9333 if (b_start_rollover)
9334 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9335 wxTIMER_ONE_SHOT);
9336 Route *tail = 0;
9337 Route *current = 0;
9338 bool appending = false;
9339 bool inserting = false;
9340 int connect = 0;
9341 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9342 // drag
9343 if (m_pRoutePointEditTarget) {
9344 // Check to see if there is a nearby point which may replace the
9345 // dragged one
9346 RoutePoint *pMousePoint = NULL;
9347
9348 int index_last;
9349 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9350 double nearby_radius_meters =
9351 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9352 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9353 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9354 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9355 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9356 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9357 bool duplicate =
9358 false; // ensure we won't create duplicate point in routes
9359 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9360 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9361 ir++) {
9362 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9363 if (pr && pr->pRoutePointList) {
9364 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9365 wxNOT_FOUND) {
9366 duplicate = true;
9367 break;
9368 }
9369 }
9370 }
9371 }
9372
9373 // Special case:
9374 // Allow "re-use" of a route's waypoints iff it is a simple
9375 // isolated route. This allows, for instance, creation of a closed
9376 // polygon route
9377 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9378
9379 if (!duplicate) {
9380 int dlg_return;
9381 dlg_return =
9382 OCPNMessageBox(this,
9383 _("Replace this RoutePoint by the nearby "
9384 "Waypoint?"),
9385 _("OpenCPN RoutePoint change"),
9386 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9387 if (dlg_return == wxID_YES) {
9388 /*double confirmation if the dragged point has been manually
9389 * created which can be important and could be deleted
9390 * unintentionally*/
9391
9392 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9393 pNearbyPoint);
9394 current =
9395 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9396
9397 if (tail && current && (tail != current)) {
9398 int dlg_return1;
9399 connect = tail->GetIndexOf(pNearbyPoint);
9400 int index_current_route =
9401 current->GetIndexOf(m_pRoutePointEditTarget);
9402 index_last = current->GetIndexOf(current->GetLastPoint());
9403 dlg_return1 = wxID_NO;
9404 if (index_last ==
9405 index_current_route) { // we are dragging the last
9406 // point of the route
9407 if (connect != tail->GetnPoints()) { // anything to do?
9408
9409 wxString dmsg(
9410 _("Last part of route to be appended to dragged "
9411 "route?"));
9412 if (connect == 1)
9413 dmsg =
9414 _("Full route to be appended to dragged route?");
9415
9416 dlg_return1 = OCPNMessageBox(
9417 this, dmsg, _("OpenCPN Route Create"),
9418 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9419 if (dlg_return1 == wxID_YES) {
9420 appending = true;
9421 }
9422 }
9423 } else if (index_current_route ==
9424 1) { // dragging the first point of the route
9425 if (connect != 1) { // anything to do?
9426
9427 wxString dmsg(
9428 _("First part of route to be inserted into dragged "
9429 "route?"));
9430 if (connect == tail->GetnPoints())
9431 dmsg = _(
9432 "Full route to be inserted into dragged route?");
9433
9434 dlg_return1 = OCPNMessageBox(
9435 this, dmsg, _("OpenCPN Route Create"),
9436 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9437 if (dlg_return1 == wxID_YES) {
9438 inserting = true;
9439 }
9440 }
9441 }
9442 }
9443
9444 if (m_pRoutePointEditTarget->IsShared()) {
9445 // dlg_return = wxID_NO;
9446 dlg_return = OCPNMessageBox(
9447 this,
9448 _("Do you really want to delete and replace this "
9449 "WayPoint") +
9450 _T("\n") + _("which has been created manually?"),
9451 ("OpenCPN RoutePoint warning"),
9452 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9453 }
9454 }
9455 if (dlg_return == wxID_YES) {
9456 pMousePoint = pNearbyPoint;
9457 if (pMousePoint->m_bIsolatedMark) {
9458 pMousePoint->SetShared(true);
9459 }
9460 pMousePoint->m_bIsolatedMark =
9461 false; // definitely no longer isolated
9462 pMousePoint->m_bIsInRoute = true;
9463 }
9464 }
9465 }
9466 }
9467 if (!pMousePoint)
9468 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9469
9470 if (m_pEditRouteArray) {
9471 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9472 ir++) {
9473 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9474 if (g_pRouteMan->IsRouteValid(pr)) {
9475 if (pMousePoint) { // remove the dragged point and insert the
9476 // nearby
9477 int nRP =
9478 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9479
9480 pSelect->DeleteAllSelectableRoutePoints(pr);
9481 pSelect->DeleteAllSelectableRouteSegments(pr);
9482
9483 pr->pRoutePointList->Insert(nRP, pMousePoint);
9484 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9485
9486 pSelect->AddAllSelectableRouteSegments(pr);
9487 pSelect->AddAllSelectableRoutePoints(pr);
9488 }
9489 pr->FinalizeForRendering();
9490 pr->UpdateSegmentDistances();
9491 if (m_bRoutePoinDragging) {
9492 // pConfig->UpdateRoute(pr);
9493 NavObj_dB::GetInstance().UpdateRoute(pr);
9494 }
9495 }
9496 }
9497 }
9498
9499 // Update the RouteProperties Dialog, if currently shown
9500 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9501 if (m_pEditRouteArray) {
9502 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9503 ir++) {
9504 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9505 if (g_pRouteMan->IsRouteValid(pr)) {
9506 if (pRoutePropDialog->GetRoute() == pr) {
9507 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9508 }
9509 /* cannot edit track points anyway
9510 else if ( ( NULL !=
9511 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9512 pTrackPropDialog->m_pTrack == pr ) {
9513 pTrackPropDialog->SetTrackAndUpdate(
9514 pr );
9515 }
9516 */
9517 }
9518 }
9519 }
9520 }
9521 if (pMousePoint) { // clear all about the dragged point
9522 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9523 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9524 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9525 // Hide mark properties dialog if open on the replaced point
9526 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9527 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9528 g_pMarkInfoDialog->Hide();
9529
9530 delete m_pRoutePointEditTarget;
9531 m_lastRoutePointEditTarget = NULL;
9532 m_pRoutePointEditTarget = NULL;
9533 undo->AfterUndoableAction(pMousePoint);
9534 undo->InvalidateUndo();
9535 }
9536 }
9537 }
9538
9539 else if (m_bMarkEditing) { // End of way point drag
9540 if (m_pRoutePointEditTarget)
9541 if (m_bRoutePoinDragging) {
9542 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9543 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9544 }
9545 }
9546
9547 if (m_pRoutePointEditTarget)
9548 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9549
9550 if (!m_pRoutePointEditTarget) {
9551 delete m_pEditRouteArray;
9552 m_pEditRouteArray = NULL;
9553 m_bRouteEditing = false;
9554 }
9555 m_bRoutePoinDragging = false;
9556
9557 if (appending) { // Appending to the route of which the last point is
9558 // dragged onto another route
9559
9560 // copy tail from connect until length to end of current after dragging
9561
9562 int length = tail->GetnPoints();
9563 for (int i = connect + 1; i <= length; i++) {
9564 current->AddPointAndSegment(tail->GetPoint(i), false);
9565 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9566 m_routeState++;
9567 gFrame->RefreshAllCanvas();
9568 ret = true;
9569 }
9570 current->FinalizeForRendering();
9571 current->m_bIsBeingEdited = false;
9572 FinishRoute();
9573 g_pRouteMan->DeleteRoute(tail);
9574 }
9575 if (inserting) {
9576 pSelect->DeleteAllSelectableRoutePoints(current);
9577 pSelect->DeleteAllSelectableRouteSegments(current);
9578 for (int i = 1; i < connect; i++) { // numbering in the tail route
9579 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9580 }
9581 pSelect->AddAllSelectableRouteSegments(current);
9582 pSelect->AddAllSelectableRoutePoints(current);
9583 current->FinalizeForRendering();
9584 current->m_bIsBeingEdited = false;
9585 g_pRouteMan->DeleteRoute(tail);
9586 }
9587
9588 // Update the RouteProperties Dialog, if currently shown
9589 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9590 if (m_pEditRouteArray) {
9591 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9592 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9593 if (g_pRouteMan->IsRouteValid(pr)) {
9594 if (pRoutePropDialog->GetRoute() == pr) {
9595 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9596 }
9597 }
9598 }
9599 }
9600 }
9601
9602 } // g_btouch
9603
9604 else { // !g_btouch
9605 if (m_bRouteEditing) { // End of RoutePoint drag
9606 Route *tail = 0;
9607 Route *current = 0;
9608 bool appending = false;
9609 bool inserting = false;
9610 int connect = 0;
9611 int index_last;
9612 if (m_pRoutePointEditTarget) {
9613 m_pRoutePointEditTarget->m_bBlink = false;
9614 // Check to see if there is a nearby point which may replace the
9615 // dragged one
9616 RoutePoint *pMousePoint = NULL;
9617 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9618 double nearby_radius_meters =
9619 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9620 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9621 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9622 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9623 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9624 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9625 bool duplicate = false; // don't create duplicate point in routes
9626 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9627 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9628 ir++) {
9629 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9630 if (pr && pr->pRoutePointList) {
9631 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9632 wxNOT_FOUND) {
9633 duplicate = true;
9634 break;
9635 }
9636 }
9637 }
9638 }
9639
9640 // Special case:
9641 // Allow "re-use" of a route's waypoints iff it is a simple
9642 // isolated route. This allows, for instance, creation of a closed
9643 // polygon route
9644 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9645
9646 if (!duplicate) {
9647 int dlg_return;
9648 dlg_return =
9649 OCPNMessageBox(this,
9650 _("Replace this RoutePoint by the nearby "
9651 "Waypoint?"),
9652 _("OpenCPN RoutePoint change"),
9653 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9654 if (dlg_return == wxID_YES) {
9655 /*double confirmation if the dragged point has been manually
9656 * created which can be important and could be deleted
9657 * unintentionally*/
9658 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9659 pNearbyPoint);
9660 current =
9661 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9662
9663 if (tail && current && (tail != current)) {
9664 int dlg_return1;
9665 connect = tail->GetIndexOf(pNearbyPoint);
9666 int index_current_route =
9667 current->GetIndexOf(m_pRoutePointEditTarget);
9668 index_last = current->GetIndexOf(current->GetLastPoint());
9669 dlg_return1 = wxID_NO;
9670 if (index_last ==
9671 index_current_route) { // we are dragging the last
9672 // point of the route
9673 if (connect != tail->GetnPoints()) { // anything to do?
9674
9675 wxString dmsg(
9676 _("Last part of route to be appended to dragged "
9677 "route?"));
9678 if (connect == 1)
9679 dmsg =
9680 _("Full route to be appended to dragged route?");
9681
9682 dlg_return1 = OCPNMessageBox(
9683 this, dmsg, _("OpenCPN Route Create"),
9684 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9685 if (dlg_return1 == wxID_YES) {
9686 appending = true;
9687 }
9688 }
9689 } else if (index_current_route ==
9690 1) { // dragging the first point of the route
9691 if (connect != 1) { // anything to do?
9692
9693 wxString dmsg(
9694 _("First part of route to be inserted into dragged "
9695 "route?"));
9696 if (connect == tail->GetnPoints())
9697 dmsg = _(
9698 "Full route to be inserted into dragged route?");
9699
9700 dlg_return1 = OCPNMessageBox(
9701 this, dmsg, _("OpenCPN Route Create"),
9702 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9703 if (dlg_return1 == wxID_YES) {
9704 inserting = true;
9705 }
9706 }
9707 }
9708 }
9709
9710 if (m_pRoutePointEditTarget->IsShared()) {
9711 dlg_return = wxID_NO;
9712 dlg_return = OCPNMessageBox(
9713 this,
9714 _("Do you really want to delete and replace this "
9715 "WayPoint") +
9716 _T("\n") + _("which has been created manually?"),
9717 ("OpenCPN RoutePoint warning"),
9718 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9719 }
9720 }
9721 if (dlg_return == wxID_YES) {
9722 pMousePoint = pNearbyPoint;
9723 if (pMousePoint->m_bIsolatedMark) {
9724 pMousePoint->SetShared(true);
9725 }
9726 pMousePoint->m_bIsolatedMark =
9727 false; // definitely no longer isolated
9728 pMousePoint->m_bIsInRoute = true;
9729 }
9730 }
9731 }
9732 }
9733 if (!pMousePoint)
9734 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9735
9736 if (m_pEditRouteArray) {
9737 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9738 ir++) {
9739 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9740 if (g_pRouteMan->IsRouteValid(pr)) {
9741 if (pMousePoint) { // replace dragged point by nearby one
9742 int nRP =
9743 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9744
9745 pSelect->DeleteAllSelectableRoutePoints(pr);
9746 pSelect->DeleteAllSelectableRouteSegments(pr);
9747
9748 pr->pRoutePointList->Insert(nRP, pMousePoint);
9749 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9750
9751 pSelect->AddAllSelectableRouteSegments(pr);
9752 pSelect->AddAllSelectableRoutePoints(pr);
9753 }
9754 pr->FinalizeForRendering();
9755 pr->UpdateSegmentDistances();
9756 pr->m_bIsBeingEdited = false;
9757
9758 if (m_bRoutePoinDragging) {
9759 // pConfig->UpdateRoute(pr);
9760 NavObj_dB::GetInstance().UpdateRoute(pr);
9761 }
9762 pr->SetHiLite(0);
9763 }
9764 }
9765 Refresh(false);
9766 }
9767
9768 if (appending) {
9769 // copy tail from connect until length to end of current after
9770 // dragging
9771
9772 int length = tail->GetnPoints();
9773 for (int i = connect + 1; i <= length; i++) {
9774 current->AddPointAndSegment(tail->GetPoint(i), false);
9775 if (current)
9776 current->m_lastMousePointIndex = current->GetnPoints();
9777 m_routeState++;
9778 gFrame->RefreshAllCanvas();
9779 ret = true;
9780 }
9781 current->FinalizeForRendering();
9782 current->m_bIsBeingEdited = false;
9783 FinishRoute();
9784 g_pRouteMan->DeleteRoute(tail);
9785 }
9786 if (inserting) {
9787 pSelect->DeleteAllSelectableRoutePoints(current);
9788 pSelect->DeleteAllSelectableRouteSegments(current);
9789 for (int i = 1; i < connect; i++) { // numbering in the tail route
9790 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9791 }
9792 pSelect->AddAllSelectableRouteSegments(current);
9793 pSelect->AddAllSelectableRoutePoints(current);
9794 current->FinalizeForRendering();
9795 current->m_bIsBeingEdited = false;
9796 g_pRouteMan->DeleteRoute(tail);
9797 }
9798
9799 // Update the RouteProperties Dialog, if currently shown
9800 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9801 if (m_pEditRouteArray) {
9802 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9803 ir++) {
9804 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9805 if (g_pRouteMan->IsRouteValid(pr)) {
9806 if (pRoutePropDialog->GetRoute() == pr) {
9807 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9808 }
9809 }
9810 }
9811 }
9812 }
9813
9814 if (pMousePoint) {
9815 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9816 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9817 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9818 // Hide mark properties dialog if open on the replaced point
9819 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9820 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9821 g_pMarkInfoDialog->Hide();
9822
9823 delete m_pRoutePointEditTarget;
9824 m_lastRoutePointEditTarget = NULL;
9825 undo->AfterUndoableAction(pMousePoint);
9826 undo->InvalidateUndo();
9827 } else {
9828 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9829 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9830
9831 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9832 }
9833
9834 delete m_pEditRouteArray;
9835 m_pEditRouteArray = NULL;
9836 }
9837
9838 InvalidateGL();
9839 m_bRouteEditing = false;
9840 m_pRoutePointEditTarget = NULL;
9841
9842 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9843 ret = true;
9844 }
9845
9846 else if (m_bMarkEditing) { // end of Waypoint drag
9847 if (m_pRoutePointEditTarget) {
9848 if (m_bRoutePoinDragging) {
9849 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9850 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9851 }
9852 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9853 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9854 if (!g_bopengl) {
9855 wxRect wp_rect;
9856 RoutePointGui(*m_pRoutePointEditTarget)
9857 .CalculateDCRect(m_dc_route, this, &wp_rect);
9858 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9859 RefreshRect(wp_rect, true);
9860 }
9861 }
9862 m_pRoutePointEditTarget = NULL;
9863 m_bMarkEditing = false;
9864 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9865 ret = true;
9866 }
9867
9868 else if (leftIsDown) { // left click for chart center
9869 leftIsDown = false;
9870 ret = false;
9871
9872 if (!g_btouch) {
9873 if (!m_bChartDragging && !m_bMeasure_Active) {
9874 } else {
9875 m_bChartDragging = false;
9876 }
9877 }
9878 }
9879 m_bRoutePoinDragging = false;
9880 } // !btouch
9881
9882 if (ret) return true;
9883 } // left up
9884
9885 if (event.RightDown()) {
9886 SetFocus(); // This is to let a plugin know which canvas is right-clicked
9887 last_drag.x = mx;
9888 last_drag.y = my;
9889
9890 if (g_btouch) {
9891 // if( m_pRoutePointEditTarget )
9892 // return false;
9893 }
9894
9895 ret = true;
9896 m_FinishRouteOnKillFocus = false;
9897 CallPopupMenu(mx, my);
9898 m_FinishRouteOnKillFocus = true;
9899 } // Right down
9900
9901 return ret;
9902}
9903
9904bool panleftIsDown;
9905
9906bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
9907 // Skip all mouse processing if shift is held.
9908 // This allows plugins to implement shift+drag behaviors.
9909 if (event.ShiftDown()) {
9910 return false;
9911 }
9912 int x, y;
9913 event.GetPosition(&x, &y);
9914
9915 x *= m_displayScale;
9916 y *= m_displayScale;
9917
9918 // Check for wheel rotation
9919 // ideally, should be just longer than the time between
9920 // processing accumulated mouse events from the event queue
9921 // as would happen during screen redraws.
9922 int wheel_dir = event.GetWheelRotation();
9923
9924 if (wheel_dir) {
9925 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
9926 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
9927
9928 double factor = g_mouse_zoom_sensitivity;
9929 if (wheel_dir < 0) factor = 1 / factor;
9930
9931 if (g_bsmoothpanzoom) {
9932 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
9933 if (wheel_dir == m_last_wheel_dir) {
9934 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
9935 // m_zoom_target /= factor;
9936 } else
9937 StopMovement();
9938 } else {
9939 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
9940 m_wheelstopwatch.Start(0);
9941 // m_zoom_target = VPoint.chart_scale / factor;
9942 }
9943 }
9944
9945 m_last_wheel_dir = wheel_dir;
9946
9947 ZoomCanvas(factor, true, false);
9948 }
9949
9950 if (event.LeftDown()) {
9951 // Skip the first left click if it will cause a canvas focus shift
9952 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
9953 // printf("focus shift\n");
9954 return false;
9955 }
9956
9957 last_drag.x = x, last_drag.y = y;
9958 panleftIsDown = true;
9959 }
9960
9961 if (event.LeftUp()) {
9962 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
9963 // seen here.
9964 panleftIsDown = false;
9965
9966 if (!g_btouch) {
9967 if (!m_bChartDragging && !m_bMeasure_Active) {
9968 switch (cursor_region) {
9969 case MID_RIGHT: {
9970 PanCanvas(100, 0);
9971 break;
9972 }
9973
9974 case MID_LEFT: {
9975 PanCanvas(-100, 0);
9976 break;
9977 }
9978
9979 case MID_TOP: {
9980 PanCanvas(0, 100);
9981 break;
9982 }
9983
9984 case MID_BOT: {
9985 PanCanvas(0, -100);
9986 break;
9987 }
9988
9989 case CENTER: {
9990 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
9991 break;
9992 }
9993 }
9994 } else {
9995 m_bChartDragging = false;
9996 }
9997 }
9998 }
9999 }
10000
10001 if (event.Dragging() && event.LeftIsDown()) {
10002 /*
10003 * fixed dragging.
10004 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10005 * before the drag event. Hence, as there is no mouse down event, last_drag
10006 * is not reset before the drag. And that results in one single drag
10007 * session, meaning you cannot drag the map a few miles north, lift your
10008 * finger, and the go even further north. Instead, the map resets itself
10009 * always to the very first drag start (since there is not reset of
10010 * last_drag).
10011 *
10012 * Besides, should not left down and dragging be enough of a situation to
10013 * start a drag procedure?
10014 *
10015 * Anyways, guarded it to be active in touch situations only.
10016 */
10017
10018 if (g_btouch) {
10019 struct timespec now;
10020 clock_gettime(CLOCK_MONOTONIC, &now);
10021 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10022
10023 if (false == m_bChartDragging) {
10024 // Reset drag calculation members
10025 last_drag.x = x, last_drag.y = y;
10026 m_bChartDragging = true;
10027 m_chart_drag_total_time = 0;
10028 m_chart_drag_total_x = 0;
10029 m_chart_drag_total_y = 0;
10030 m_inertia_last_drag_x = x;
10031 m_inertia_last_drag_y = y;
10032 m_drag_vec_x.clear();
10033 m_drag_vec_y.clear();
10034 m_drag_vec_t.clear();
10035 m_last_drag_time = tnow;
10036 }
10037
10038 // Calculate and store drag dynamics.
10039 uint64_t delta_t = tnow - m_last_drag_time;
10040 double delta_tf = delta_t / 1e9;
10041
10042 m_chart_drag_total_time += delta_tf;
10043 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10044 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10045
10046 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10047 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10048 m_drag_vec_t.push_back(delta_tf);
10049
10050 m_inertia_last_drag_x = x;
10051 m_inertia_last_drag_y = y;
10052 m_last_drag_time = tnow;
10053
10054 if ((last_drag.x != x) || (last_drag.y != y)) {
10055 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10056 // dragging on route create.
10057 // github #2994
10058 m_bChartDragging = true;
10059 StartTimedMovement();
10060 m_pan_drag.x += last_drag.x - x;
10061 m_pan_drag.y += last_drag.y - y;
10062 last_drag.x = x, last_drag.y = y;
10063 }
10064 }
10065 } else {
10066 if ((last_drag.x != x) || (last_drag.y != y)) {
10067 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10068 // dragging on route create.
10069 // github #2994
10070 m_bChartDragging = true;
10071 StartTimedMovement();
10072 m_pan_drag.x += last_drag.x - x;
10073 m_pan_drag.y += last_drag.y - y;
10074 last_drag.x = x, last_drag.y = y;
10075 }
10076 }
10077 }
10078
10079 // Handle some special cases
10080 if (g_btouch) {
10081 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10082 // deactivate next LeftUp to ovoid creating an unexpected point
10083 m_DoubleClickTimer->Start();
10084 singleClickEventIsValid = false;
10085 }
10086 }
10087 }
10088
10089 return true;
10090}
10091
10092void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10093 if (MouseEventOverlayWindows(event)) return;
10094
10095 if (MouseEventSetup(event)) return; // handled, no further action required
10096
10097 if (!MouseEventProcessObjects(event)) MouseEventProcessCanvas(event);
10098}
10099
10100void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10101 // Switch to the appropriate cursor on mouse movement
10102
10103 wxCursor *ptarget_cursor = pCursorArrow;
10104 if (!pPlugIn_Cursor) {
10105 ptarget_cursor = pCursorArrow;
10106 if ((!m_routeState) &&
10107 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10108 if (cursor_region == MID_RIGHT) {
10109 ptarget_cursor = pCursorRight;
10110 } else if (cursor_region == MID_LEFT) {
10111 ptarget_cursor = pCursorLeft;
10112 } else if (cursor_region == MID_TOP) {
10113 ptarget_cursor = pCursorDown;
10114 } else if (cursor_region == MID_BOT) {
10115 ptarget_cursor = pCursorUp;
10116 } else {
10117 ptarget_cursor = pCursorArrow;
10118 }
10119 } else if (m_bMeasure_Active ||
10120 m_routeState) // If Measure tool use Pencil Cursor
10121 ptarget_cursor = pCursorPencil;
10122 } else {
10123 ptarget_cursor = pPlugIn_Cursor;
10124 }
10125
10126 SetCursor(*ptarget_cursor);
10127}
10128
10129void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10130 SetCursor(*pCursorArrow);
10131}
10132
10133void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10134 ChartPlugInWrapper *target_plugin_chart = NULL;
10135 s57chart *Chs57 = NULL;
10136 wxFileName file;
10137 wxArrayString files;
10138
10139 ChartBase *target_chart = GetChartAtCursor();
10140 if (target_chart) {
10141 file.Assign(target_chart->GetFullPath());
10142 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10143 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10144 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10145 else
10146 Chs57 = dynamic_cast<s57chart *>(target_chart);
10147 } else { // target_chart = null, might be mbtiles
10148 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10149 unsigned int im = stackIndexArray.size();
10150 int scale = 2147483647; // max 32b integer
10151 if (VPoint.b_quilt && im > 0) {
10152 for (unsigned int is = 0; is < im; is++) {
10153 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10154 CHART_TYPE_MBTILES) {
10155 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10156 double lat, lon;
10157 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10158 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10159 .GetBBox()
10160 .Contains(lat, lon)) {
10161 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10162 scale) {
10163 scale =
10164 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10165 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10166 }
10167 }
10168 }
10169 }
10170 }
10171 }
10172
10173 std::vector<Ais8_001_22 *> area_notices;
10174
10175 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10176 float vp_scale = GetVPScale();
10177
10178 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10179 auto target_data = target.second;
10180 if (!target_data->area_notices.empty()) {
10181 for (auto &ani : target_data->area_notices) {
10182 Ais8_001_22 &area_notice = ani.second;
10183
10184 BoundingBox bbox;
10185
10186 for (Ais8_001_22_SubAreaList::iterator sa =
10187 area_notice.sub_areas.begin();
10188 sa != area_notice.sub_areas.end(); ++sa) {
10189 switch (sa->shape) {
10190 case AIS8_001_22_SHAPE_CIRCLE: {
10191 wxPoint target_point;
10192 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10193 bbox.Expand(target_point);
10194 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10195 break;
10196 }
10197 case AIS8_001_22_SHAPE_RECT: {
10198 wxPoint target_point;
10199 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10200 bbox.Expand(target_point);
10201 if (sa->e_dim_m > sa->n_dim_m)
10202 bbox.EnLarge(sa->e_dim_m * vp_scale);
10203 else
10204 bbox.EnLarge(sa->n_dim_m * vp_scale);
10205 break;
10206 }
10207 case AIS8_001_22_SHAPE_POLYGON:
10208 case AIS8_001_22_SHAPE_POLYLINE: {
10209 for (int i = 0; i < 4; ++i) {
10210 double lat = sa->latitude;
10211 double lon = sa->longitude;
10212 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10213 &lat, &lon);
10214 wxPoint target_point;
10215 GetCanvasPointPix(lat, lon, &target_point);
10216 bbox.Expand(target_point);
10217 }
10218 break;
10219 }
10220 case AIS8_001_22_SHAPE_SECTOR: {
10221 double lat1 = sa->latitude;
10222 double lon1 = sa->longitude;
10223 double lat, lon;
10224 wxPoint target_point;
10225 GetCanvasPointPix(lat1, lon1, &target_point);
10226 bbox.Expand(target_point);
10227 for (int i = 0; i < 18; ++i) {
10228 ll_gc_ll(
10229 lat1, lon1,
10230 sa->left_bound_deg +
10231 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10232 sa->radius_m / 1852.0, &lat, &lon);
10233 GetCanvasPointPix(lat, lon, &target_point);
10234 bbox.Expand(target_point);
10235 }
10236 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10237 &lat, &lon);
10238 GetCanvasPointPix(lat, lon, &target_point);
10239 bbox.Expand(target_point);
10240 break;
10241 }
10242 }
10243 }
10244
10245 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10246 area_notices.push_back(&area_notice);
10247 }
10248 }
10249 }
10250 }
10251 }
10252
10253 if (target_chart || !area_notices.empty() || file.HasName()) {
10254 // Go get the array of all objects at the cursor lat/lon
10255 int sel_rad_pix = 5;
10256 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10257
10258 // Make sure we always get the lights from an object, even if we are
10259 // currently not displaying lights on the chart.
10260
10261 SetCursor(wxCURSOR_WAIT);
10262 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10263 if (!lightsVis) SetShowENCLights(true);
10264 ;
10265
10266 ListOfObjRazRules *rule_list = NULL;
10267 ListOfPI_S57Obj *pi_rule_list = NULL;
10268 if (Chs57)
10269 rule_list =
10270 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10271 else if (target_plugin_chart)
10272 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10273 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10274
10275 ListOfObjRazRules *overlay_rule_list = NULL;
10276 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10277 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10278
10279 if (CHs57_Overlay) {
10280 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10281 zlat, zlon, SelectRadius, &GetVP());
10282 }
10283
10284 if (!lightsVis) SetShowENCLights(false);
10285
10286 wxString objText;
10287 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10288 wxString face = dFont->GetFaceName();
10289
10290 if (NULL == g_pObjectQueryDialog) {
10291 g_pObjectQueryDialog =
10292 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10293 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10294 }
10295
10296 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10297 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10298
10299#ifdef __WXOSX__
10300 // Auto Adjustment for dark mode
10301 fg = g_pObjectQueryDialog->GetForegroundColour();
10302#endif
10303
10304 objText.Printf(
10305 _T("<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>"),
10306 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10307
10308#ifdef __WXOSX__
10309 int points = dFont->GetPointSize();
10310#else
10311 int points = dFont->GetPointSize() + 1;
10312#endif
10313
10314 int sizes[7];
10315 for (int i = -2; i < 5; i++) {
10316 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10317 }
10318 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10319
10320 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += _T("<i>");
10321
10322 if (overlay_rule_list && CHs57_Overlay) {
10323 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10324 objText << _T("<hr noshade>");
10325 }
10326
10327 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10328 an != area_notices.end(); ++an) {
10329 objText << _T( "<b>AIS Area Notice:</b> " );
10330 objText << ais8_001_22_notice_names[(*an)->notice_type];
10331 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10332 (*an)->sub_areas.begin();
10333 sa != (*an)->sub_areas.end(); ++sa)
10334 if (!sa->text.empty()) objText << sa->text;
10335 objText << _T( "<br>expires: " ) << (*an)->expiry_time.Format();
10336 objText << _T( "<hr noshade>" );
10337 }
10338
10339 if (Chs57)
10340 objText << Chs57->CreateObjDescriptions(rule_list);
10341 else if (target_plugin_chart)
10342 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10343 pi_rule_list);
10344
10345 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << _T("</i>");
10346
10347 // Add the additional info files
10348 wxString AddFiles, filenameOK;
10349 int filecount = 0;
10350 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10351 // plugin
10352
10353 AddFiles = wxString::Format(
10354 _T("<hr noshade><br><b>Additional info files attached to: </b> ")
10355 _T("<font ")
10356 _T("size=-2>%s</font><br><table border=0 cellspacing=0 ")
10357 _T("cellpadding=3>"),
10358 file.GetFullName());
10359 file.Normalize();
10360 file.Assign(file.GetPath(), wxT(""));
10361 wxDir dir(file.GetFullPath());
10362 wxString filename;
10363 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10364 while (cont) {
10365 file.Assign(dir.GetNameWithSep().append(filename));
10366 wxString FormatString =
10367 _T("<td valign=top><font size=-2><a ")
10368 _T("href=\"%s\">%s</a></font></td>");
10369 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10370 filenameOK = file.GetFullPath(); // remember last valid name
10371 // we are making a 3 columns table. New row only every third file
10372 if (3 * ((int)filecount / 3) == filecount)
10373 FormatString.Prepend(_T("<tr>")); // new row
10374 else
10375 FormatString.Prepend(
10376 _T("<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>")); // an empty
10377 // spacer column
10378
10379 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10380 file.GetFullName());
10381 filecount++;
10382 }
10383 cont = dir.GetNext(&filename);
10384 }
10385 objText << AddFiles << _T("</table>");
10386 }
10387 objText << _T("</font>");
10388 objText << _T("</body></html>");
10389
10390 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10391 g_pObjectQueryDialog->SetHTMLPage(objText);
10392 g_pObjectQueryDialog->Show();
10393 }
10394 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10395 // generate an event to avoid double code
10396 wxHtmlLinkInfo hli(filenameOK);
10397 wxHtmlLinkEvent hle(1, hli);
10398 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10399 }
10400
10401 if (rule_list) rule_list->Clear();
10402 delete rule_list;
10403
10404 if (overlay_rule_list) overlay_rule_list->Clear();
10405 delete overlay_rule_list;
10406
10407 if (pi_rule_list) pi_rule_list->Clear();
10408 delete pi_rule_list;
10409
10410 SetCursor(wxCURSOR_ARROW);
10411 }
10412}
10413
10414void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10415 bool bNew = false;
10416 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10417 // Dialog
10418 g_pMarkInfoDialog = new MarkInfoDlg(this);
10419 bNew = true;
10420 }
10421
10422 if (1 /*g_bresponsive*/) {
10423 wxSize canvas_size = GetSize();
10424
10425 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10426 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10427
10428 g_pMarkInfoDialog->Layout();
10429
10430 wxPoint canvas_pos = GetPosition();
10431 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10432
10433 bool newFit = false;
10434 if (canvas_size.x < fitted_size.x) {
10435 fitted_size.x = canvas_size.x - 40;
10436 if (canvas_size.y < fitted_size.y)
10437 fitted_size.y -= 40; // scrollbar added
10438 }
10439 if (canvas_size.y < fitted_size.y) {
10440 fitted_size.y = canvas_size.y - 40;
10441 if (canvas_size.x < fitted_size.x)
10442 fitted_size.x -= 40; // scrollbar added
10443 }
10444
10445 if (newFit) {
10446 g_pMarkInfoDialog->SetSize(fitted_size);
10447 g_pMarkInfoDialog->Centre();
10448 }
10449 }
10450
10451 markPoint->m_bRPIsBeingEdited = false;
10452
10453 wxString title_base = _("Mark Properties");
10454 if (markPoint->m_bIsInRoute) {
10455 title_base = _("Waypoint Properties");
10456 }
10457 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10458 g_pMarkInfoDialog->UpdateProperties();
10459 if (markPoint->m_bIsInLayer) {
10460 wxString caption(wxString::Format(_T("%s, %s: %s"), title_base, _("Layer"),
10461 GetLayerName(markPoint->m_LayerID)));
10462 g_pMarkInfoDialog->SetDialogTitle(caption);
10463 } else
10464 g_pMarkInfoDialog->SetDialogTitle(title_base);
10465
10466 g_pMarkInfoDialog->Show();
10467 g_pMarkInfoDialog->Raise();
10468 g_pMarkInfoDialog->InitialFocus();
10469 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10470}
10471
10472void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10473 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10474 pRoutePropDialog->SetRouteAndUpdate(selected);
10475 // pNew->UpdateProperties();
10476 pRoutePropDialog->Show();
10477 pRoutePropDialog->Raise();
10478 return;
10479 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10480 this); // There is one global instance of the RouteProp Dialog
10481
10482 if (g_bresponsive) {
10483 wxSize canvas_size = GetSize();
10484 wxPoint canvas_pos = GetPosition();
10485 wxSize fitted_size = pRoutePropDialog->GetSize();
10486 ;
10487
10488 if (canvas_size.x < fitted_size.x) {
10489 fitted_size.x = canvas_size.x;
10490 if (canvas_size.y < fitted_size.y)
10491 fitted_size.y -= 20; // scrollbar added
10492 }
10493 if (canvas_size.y < fitted_size.y) {
10494 fitted_size.y = canvas_size.y;
10495 if (canvas_size.x < fitted_size.x)
10496 fitted_size.x -= 20; // scrollbar added
10497 }
10498
10499 pRoutePropDialog->SetSize(fitted_size);
10500 pRoutePropDialog->Centre();
10501
10502 // int xp = (canvas_size.x - fitted_size.x)/2;
10503 // int yp = (canvas_size.y - fitted_size.y)/2;
10504
10505 wxPoint xxp = ClientToScreen(canvas_pos);
10506 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10507 }
10508
10509 pRoutePropDialog->SetRouteAndUpdate(selected);
10510
10511 pRoutePropDialog->Show();
10512
10513 Refresh(false);
10514}
10515
10516void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10517 pTrackPropDialog = TrackPropDlg::getInstance(
10518 this); // There is one global instance of the RouteProp Dialog
10519
10520 pTrackPropDialog->SetTrackAndUpdate(selected);
10521 pTrackPropDialog->UpdateProperties();
10522
10523 pTrackPropDialog->Show();
10524
10525 Refresh(false);
10526}
10527
10528void pupHandler_PasteWaypoint() {
10529 Kml kml;
10530
10531 int pasteBuffer = kml.ParsePasteBuffer();
10532 RoutePoint *pasted = kml.GetParsedRoutePoint();
10533 if (!pasted) return;
10534
10535 double nearby_radius_meters =
10536 g_Platform->GetSelectRadiusPix() /
10537 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10538
10539 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10540 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10541
10542 int answer = wxID_NO;
10543 if (nearPoint && !nearPoint->m_bIsInLayer) {
10544 wxString msg;
10545 msg << _(
10546 "There is an existing waypoint at the same location as the one you are "
10547 "pasting. Would you like to merge the pasted data with it?\n\n");
10548 msg << _("Answering 'No' will create a new waypoint at the same location.");
10549 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10550 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10551 }
10552
10553 if (answer == wxID_YES) {
10554 nearPoint->SetName(pasted->GetName());
10555 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10556 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10557 pRouteManagerDialog->UpdateWptListCtrl();
10558 }
10559
10560 if (answer == wxID_NO) {
10561 RoutePoint *newPoint = new RoutePoint(pasted);
10562 newPoint->m_bIsolatedMark = true;
10563 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10564 newPoint);
10565 // pConfig->AddNewWayPoint(newPoint, -1);
10566 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10567
10568 pWayPointMan->AddRoutePoint(newPoint);
10569 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10570 pRouteManagerDialog->UpdateWptListCtrl();
10571 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10572 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10573 }
10574
10575 gFrame->InvalidateAllGL();
10576 gFrame->RefreshAllCanvas(false);
10577}
10578
10579void pupHandler_PasteRoute() {
10580 Kml kml;
10581
10582 int pasteBuffer = kml.ParsePasteBuffer();
10583 Route *pasted = kml.GetParsedRoute();
10584 if (!pasted) return;
10585
10586 double nearby_radius_meters =
10587 g_Platform->GetSelectRadiusPix() /
10588 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10589
10590 RoutePoint *curPoint;
10591 RoutePoint *nearPoint;
10592 RoutePoint *prevPoint = NULL;
10593
10594 bool mergepoints = false;
10595 bool createNewRoute = true;
10596 int existingWaypointCounter = 0;
10597
10598 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10599 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10600 nearPoint = pWayPointMan->GetNearbyWaypoint(
10601 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10602 if (nearPoint) {
10603 mergepoints = true;
10604 existingWaypointCounter++;
10605 // Small hack here to avoid both extending RoutePoint and repeating all
10606 // the GetNearbyWaypoint calculations. Use existin data field in
10607 // RoutePoint as temporary storage.
10608 curPoint->m_bPtIsSelected = true;
10609 }
10610 }
10611
10612 int answer = wxID_NO;
10613 if (mergepoints) {
10614 wxString msg;
10615 msg << _(
10616 "There are existing waypoints at the same location as some of the ones "
10617 "you are pasting. Would you like to just merge the pasted data into "
10618 "them?\n\n");
10619 msg << _("Answering 'No' will create all new waypoints for this route.");
10620 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10621 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10622
10623 if (answer == wxID_CANCEL) {
10624 return;
10625 }
10626 }
10627
10628 // If all waypoints exist since before, and a route with the same name, we
10629 // don't create a new route.
10630 if (mergepoints && answer == wxID_YES &&
10631 existingWaypointCounter == pasted->GetnPoints()) {
10632 wxRouteListNode *route_node = pRouteList->GetFirst();
10633 while (route_node) {
10634 Route *proute = route_node->GetData();
10635
10636 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10637 createNewRoute = false;
10638 break;
10639 }
10640 route_node = route_node->GetNext();
10641 }
10642 }
10643
10644 Route *newRoute = 0;
10645 RoutePoint *newPoint = 0;
10646
10647 if (createNewRoute) {
10648 newRoute = new Route();
10649 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10650 }
10651
10652 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10653 curPoint = pasted->GetPoint(i);
10654 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10655 curPoint->m_bPtIsSelected = false;
10656 newPoint = pWayPointMan->GetNearbyWaypoint(
10657 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10658 newPoint->SetName(curPoint->GetName());
10659 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10660
10661 if (createNewRoute) newRoute->AddPoint(newPoint);
10662 } else {
10663 curPoint->m_bPtIsSelected = false;
10664
10665 newPoint = new RoutePoint(curPoint);
10666 newPoint->m_bIsolatedMark = false;
10667 newPoint->SetIconName(_T("circle"));
10668 newPoint->m_bIsVisible = true;
10669 newPoint->m_bShowName = false;
10670 newPoint->SetShared(false);
10671
10672 newRoute->AddPoint(newPoint);
10673 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10674 newPoint);
10675 // pConfig->AddNewWayPoint(newPoint, -1);
10676 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10677 pWayPointMan->AddRoutePoint(newPoint);
10678 }
10679 if (i > 1 && createNewRoute)
10680 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10681 curPoint->m_lat, curPoint->m_lon,
10682 prevPoint, newPoint, newRoute);
10683 prevPoint = newPoint;
10684 }
10685
10686 if (createNewRoute) {
10687 pRouteList->Append(newRoute);
10688 // pConfig->AddNewRoute(newRoute); // use auto next num
10689 NavObj_dB::GetInstance().InsertRoute(newRoute);
10690
10691 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10692 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10693 }
10694
10695 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10696 pRouteManagerDialog->UpdateRouteListCtrl();
10697 pRouteManagerDialog->UpdateWptListCtrl();
10698 }
10699 gFrame->InvalidateAllGL();
10700 gFrame->RefreshAllCanvas(false);
10701 }
10702 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10703 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10704}
10705
10706void pupHandler_PasteTrack() {
10707 Kml kml;
10708
10709 int pasteBuffer = kml.ParsePasteBuffer();
10710 Track *pasted = kml.GetParsedTrack();
10711 if (!pasted) return;
10712
10713 TrackPoint *curPoint;
10714
10715 Track *newTrack = new Track();
10716 TrackPoint *newPoint;
10717 TrackPoint *prevPoint = NULL;
10718
10719 newTrack->SetName(pasted->GetName());
10720
10721 for (int i = 0; i < pasted->GetnPoints(); i++) {
10722 curPoint = pasted->GetPoint(i);
10723
10724 newPoint = new TrackPoint(curPoint);
10725
10726 wxDateTime now = wxDateTime::Now();
10727 newPoint->SetCreateTime(curPoint->GetCreateTime());
10728
10729 newTrack->AddPoint(newPoint);
10730
10731 if (prevPoint)
10732 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10733 newPoint->m_lat, newPoint->m_lon,
10734 prevPoint, newPoint, newTrack);
10735
10736 prevPoint = newPoint;
10737 }
10738
10739 g_TrackList.push_back(newTrack);
10740 // pConfig->AddNewTrack(newTrack);
10741 NavObj_dB::GetInstance().InsertTrack(newTrack);
10742
10743 gFrame->InvalidateAllGL();
10744 gFrame->RefreshAllCanvas(false);
10745}
10746
10747bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
10748 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
10749 m_pFoundRoutePoint, m_FoundAIS_MMSI,
10750 m_pIDXCandidate, m_nmea_log);
10751
10752 Connect(
10753 wxEVT_COMMAND_MENU_SELECTED,
10754 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10755
10756 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
10757
10758 Disconnect(
10759 wxEVT_COMMAND_MENU_SELECTED,
10760 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10761
10762 delete m_canvasMenu;
10763 m_canvasMenu = NULL;
10764
10765#ifdef __WXQT__
10766 // gFrame->SurfaceToolbar();
10767 // g_MainToolbar->Raise();
10768#endif
10769
10770 return true;
10771}
10772
10773void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
10774 // Pass menu events from the canvas to the menu handler
10775 // This is necessarily in ChartCanvas since that is the menu's parent.
10776 if (m_canvasMenu) {
10777 m_canvasMenu->PopupMenuHandler(event);
10778 }
10779 return;
10780}
10781
10782void ChartCanvas::StartRoute(void) {
10783 // Do not allow more than one canvas to create a route at one time.
10784 if (g_brouteCreating) return;
10785
10786 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
10787
10788 g_brouteCreating = true;
10789 m_routeState = 1;
10790 m_bDrawingRoute = false;
10791 SetCursor(*pCursorPencil);
10792 // SetCanvasToolbarItemState(ID_ROUTE, true);
10793 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
10794
10795 HideGlobalToolbar();
10796
10797#ifdef __ANDROID__
10798 androidSetRouteAnnunciator(true);
10799#endif
10800}
10801
10802void ChartCanvas::FinishRoute(void) {
10803 m_routeState = 0;
10804 m_prev_pMousePoint = NULL;
10805 m_bDrawingRoute = false;
10806
10807 // SetCanvasToolbarItemState(ID_ROUTE, false);
10808 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
10809#ifdef __ANDROID__
10810 androidSetRouteAnnunciator(false);
10811#endif
10812
10813 SetCursor(*pCursorArrow);
10814
10815 if (m_pMouseRoute) {
10816 if (m_bAppendingRoute) {
10817 // pConfig->UpdateRoute(m_pMouseRoute);
10818 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
10819 } else {
10820 if (m_pMouseRoute->GetnPoints() > 1) {
10821 // pConfig->AddNewRoute(m_pMouseRoute);
10822 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
10823 } else {
10824 g_pRouteMan->DeleteRoute(m_pMouseRoute);
10825 m_pMouseRoute = NULL;
10826 }
10827 }
10828 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
10829
10830 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
10831 (pRoutePropDialog->IsShown())) {
10832 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
10833 }
10834
10835 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
10836 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10837 pRouteManagerDialog->UpdateRouteListCtrl();
10838 }
10839 }
10840 m_bAppendingRoute = false;
10841 m_pMouseRoute = NULL;
10842
10843 m_pSelectedRoute = NULL;
10844
10845 undo->InvalidateUndo();
10846 gFrame->RefreshAllCanvas(true);
10847
10848 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
10849
10850 ShowGlobalToolbar();
10851
10852 g_brouteCreating = false;
10853}
10854
10855void ChartCanvas::HideGlobalToolbar() {
10856 if (m_canvasIndex == 0) {
10857 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
10858 }
10859}
10860
10861void ChartCanvas::ShowGlobalToolbar() {
10862 if (m_canvasIndex == 0) {
10863 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
10864 }
10865}
10866
10867void ChartCanvas::ShowAISTargetList(void) {
10868 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
10869 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
10870 }
10871
10872 g_pAISTargetList->UpdateAISTargetList();
10873}
10874
10875void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
10876 if (!m_bShowOutlines) return;
10877
10878 if (!ChartData) return;
10879
10880 int nEntry = ChartData->GetChartTableEntries();
10881
10882 for (int i = 0; i < nEntry; i++) {
10883 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
10884
10885 // Check to see if the candidate chart is in the currently active group
10886 bool b_group_draw = false;
10887 if (m_groupIndex > 0) {
10888 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
10889 int index = pt->GetGroupArray()[ig];
10890 if (m_groupIndex == index) {
10891 b_group_draw = true;
10892 break;
10893 }
10894 }
10895 } else
10896 b_group_draw = true;
10897
10898 if (b_group_draw) RenderChartOutline(dc, i, vp);
10899 }
10900
10901 // On CM93 Composite Charts, draw the outlines of the next smaller
10902 // scale cell
10903 cm93compchart *pcm93 = NULL;
10904 if (VPoint.b_quilt) {
10905 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
10906 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
10907 pcm93 = (cm93compchart *)pch;
10908 break;
10909 }
10910 } else if (m_singleChart &&
10911 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
10912 pcm93 = (cm93compchart *)m_singleChart;
10913
10914 if (pcm93) {
10915 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
10916 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
10917
10918 if (zoom_factor > 8.0) {
10919 wxPen mPen(GetGlobalColor(_T("UINFM")), 2, wxPENSTYLE_SHORT_DASH);
10920 dc.SetPen(mPen);
10921 } else {
10922 wxPen mPen(GetGlobalColor(_T("UINFM")), 1, wxPENSTYLE_SOLID);
10923 dc.SetPen(mPen);
10924 }
10925
10926 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
10927 }
10928}
10929
10930void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
10931#ifdef ocpnUSE_GL
10932 if (g_bopengl && m_glcc) {
10933 /* opengl version specially optimized */
10934 m_glcc->RenderChartOutline(dc, dbIndex, vp);
10935 return;
10936 }
10937#endif
10938
10939 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
10940 if (!ChartData->IsChartAvailable(dbIndex)) return;
10941 }
10942
10943 float plylat, plylon;
10944 float plylat1, plylon1;
10945
10946 int pixx, pixy, pixx1, pixy1;
10947
10948 LLBBox box;
10949 ChartData->GetDBBoundingBox(dbIndex, box);
10950
10951 // Don't draw an outline in the case where the chart covers the entire world
10952 // */
10953 if (box.GetLonRange() == 360) return;
10954
10955 double lon_bias = 0;
10956 // chart is outside of viewport lat/lon bounding box
10957 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
10958
10959 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10960
10961 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
10962 dc.SetPen(wxPen(GetGlobalColor(_T ( "YELO1" )), 1, wxPENSTYLE_SOLID));
10963
10964 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
10965 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFG" )), 1, wxPENSTYLE_SOLID));
10966
10967 else
10968 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFR" )), 1, wxPENSTYLE_SOLID));
10969
10970 // Are there any aux ply entries?
10971 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
10972 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
10973 {
10974 wxPoint r, r1;
10975
10976 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10977 plylon += lon_bias;
10978
10979 GetCanvasPointPix(plylat, plylon, &r);
10980 pixx = r.x;
10981 pixy = r.y;
10982
10983 for (int i = 0; i < nPly - 1; i++) {
10984 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
10985 plylon1 += lon_bias;
10986
10987 GetCanvasPointPix(plylat1, plylon1, &r1);
10988 pixx1 = r1.x;
10989 pixy1 = r1.y;
10990
10991 int pixxs1 = pixx1;
10992 int pixys1 = pixy1;
10993
10994 bool b_skip = false;
10995
10996 if (vp.chart_scale > 5e7) {
10997 // calculate projected distance between these two points in meters
10998 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
10999 pow((double)(pixy1 - pixy), 2)) /
11000 vp.view_scale_ppm;
11001
11002 if (dist > 0.0) {
11003 // calculate GC distance between these two points in meters
11004 double distgc =
11005 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11006
11007 // If the distances are nonsense, it means that the scale is very
11008 // small and the segment wrapped the world So skip it....
11009 // TODO improve this to draw two segments
11010 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11011 b_skip = true;
11012 } else
11013 b_skip = true;
11014 }
11015
11016 ClipResult res = cohen_sutherland_line_clip_i(
11017 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11018 if (res != Invisible && !b_skip)
11019 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11020
11021 plylat = plylat1;
11022 plylon = plylon1;
11023 pixx = pixxs1;
11024 pixy = pixys1;
11025 }
11026
11027 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11028 plylon1 += lon_bias;
11029
11030 GetCanvasPointPix(plylat1, plylon1, &r1);
11031 pixx1 = r1.x;
11032 pixy1 = r1.y;
11033
11034 ClipResult res = cohen_sutherland_line_clip_i(
11035 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11036 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11037 }
11038
11039 else // Use Aux PlyPoints
11040 {
11041 wxPoint r, r1;
11042
11043 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11044 for (int j = 0; j < nAuxPlyEntries; j++) {
11045 int nAuxPly =
11046 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11047 GetCanvasPointPix(plylat, plylon, &r);
11048 pixx = r.x;
11049 pixy = r.y;
11050
11051 for (int i = 0; i < nAuxPly - 1; i++) {
11052 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11053
11054 GetCanvasPointPix(plylat1, plylon1, &r1);
11055 pixx1 = r1.x;
11056 pixy1 = r1.y;
11057
11058 int pixxs1 = pixx1;
11059 int pixys1 = pixy1;
11060
11061 bool b_skip = false;
11062
11063 if (vp.chart_scale > 5e7) {
11064 // calculate projected distance between these two points in meters
11065 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11066 ((pixy1 - pixy) * (pixy1 - pixy))) /
11067 vp.view_scale_ppm;
11068 if (dist > 0.0) {
11069 // calculate GC distance between these two points in meters
11070 double distgc =
11071 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11072
11073 // If the distances are nonsense, it means that the scale is very
11074 // small and the segment wrapped the world So skip it....
11075 // TODO improve this to draw two segments
11076 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11077 b_skip = true;
11078 } else
11079 b_skip = true;
11080 }
11081
11082 ClipResult res = cohen_sutherland_line_clip_i(
11083 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11084 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11085
11086 plylat = plylat1;
11087 plylon = plylon1;
11088 pixx = pixxs1;
11089 pixy = pixys1;
11090 }
11091
11092 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11093 GetCanvasPointPix(plylat1, plylon1, &r1);
11094 pixx1 = r1.x;
11095 pixy1 = r1.y;
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) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11100 }
11101 }
11102}
11103
11104static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
11105 const wxString &second) {
11106 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11107
11108 int pointsize = dFont->GetPointSize();
11109 pointsize /= OCPN_GetWinDIPScaleFactor();
11110
11111 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11112 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11113 false, dFont->GetFaceName());
11114
11115 dc.SetFont(*psRLI_font);
11116
11117 int w1, h1;
11118 int w2 = 0;
11119 int h2 = 0;
11120 int h, w;
11121
11122 int xp, yp;
11123 int hilite_offset = 3;
11124#ifdef __WXMAC__
11125 wxScreenDC sdc;
11126 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
11127 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
11128#else
11129 dc.GetTextExtent(first, &w1, &h1);
11130 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
11131#endif
11132
11133 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11134 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11135
11136 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
11137 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11138
11139 h = h1 + h2;
11140
11141 xp = ref_point.x - w;
11142 yp = ref_point.y;
11143 yp += hilite_offset;
11144
11145 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor(_T ( "YELO1" )), 172);
11146
11147 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" ))));
11148 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
11149
11150 dc.DrawText(first, xp, yp);
11151 if (second.Len()) dc.DrawText(second, xp, yp + h1);
11152}
11153
11154void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11155 if (!g_bAllowShipToActive) return;
11156
11157 Route *rt = g_pRouteMan->GetpActiveRoute();
11158 if (!rt) return;
11159
11160 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11161 wxPoint2DDouble pa, pb;
11162 GetDoubleCanvasPointPix(gLat, gLon, &pa);
11163 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11164
11165 // set pen
11166 int width =
11167 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11168 if (rt->m_width != wxPENSTYLE_INVALID)
11169 width = rt->m_width; // set route pen style if any
11170 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11171 g_shipToActiveStyle, 5)]; // get setting pen style
11172 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11173 wxColour color =
11174 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11175 : // set setting route pen color
11176 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11177 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11178
11179 dc.SetPen(*mypen);
11180 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11181
11182 if (!Use_Opengl)
11183 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11184 (int)pb.m_y, GetVP(), true);
11185
11186#ifdef ocpnUSE_GL
11187 else {
11188#ifdef USE_ANDROID_GLES2
11189 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11190#else
11191 if (style != wxPENSTYLE_SOLID) {
11192 if (glChartCanvas::dash_map.find(style) !=
11193 glChartCanvas::dash_map.end()) {
11194 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11195 dc.SetPen(*mypen);
11196 }
11197 }
11198 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11199#endif
11200
11201 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11202 (int)pb.m_x, (int)pb.m_y, GetVP());
11203 }
11204#endif
11205 }
11206}
11207
11208void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11209 Route *route = 0;
11210 if (m_routeState >= 2) route = m_pMouseRoute;
11211 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11212 route = m_pMeasureRoute;
11213
11214 if (!route) return;
11215
11216 // Validate route pointer
11217 if (!g_pRouteMan->IsRouteValid(route)) return;
11218
11219 double render_lat = m_cursor_lat;
11220 double render_lon = m_cursor_lon;
11221
11222 int np = route->GetnPoints();
11223 if (np) {
11224 if (g_btouch && (np > 1)) np--;
11225 RoutePoint rp = route->GetPoint(np);
11226 render_lat = rp.m_lat;
11227 render_lon = rp.m_lon;
11228 }
11229
11230 double rhumbBearing, rhumbDist;
11231 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11232 &rhumbBearing, &rhumbDist);
11233 double brg = rhumbBearing;
11234 double dist = rhumbDist;
11235
11236 // Skip GreatCircle rubberbanding on touch devices.
11237 if (!g_btouch) {
11238 double gcBearing, gcBearing2, gcDist;
11239 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11240 m_cursor_lat, &gcDist, &gcBearing,
11241 &gcBearing2);
11242 double gcDistm = gcDist / 1852.0;
11243
11244 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11245 rhumbBearing = 90.;
11246
11247 wxPoint destPoint, lastPoint;
11248
11249 route->m_NextLegGreatCircle = false;
11250 int milesDiff = rhumbDist - gcDistm;
11251 if (milesDiff > 1) {
11252 brg = gcBearing;
11253 dist = gcDistm;
11254 route->m_NextLegGreatCircle = true;
11255 }
11256
11257 // FIXME (MacOS, the first segment is rendered wrong)
11258 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11259 &lastPoint);
11260
11261 if (route->m_NextLegGreatCircle) {
11262 for (int i = 1; i <= milesDiff; i++) {
11263 double p = (double)i * (1.0 / (double)milesDiff);
11264 double pLat, pLon;
11265 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11266 &pLon, &pLat, &gcBearing2);
11267 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11268 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11269 false);
11270 lastPoint = destPoint;
11271 }
11272 } else {
11273 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11274 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11275 false);
11276 if (m_bMeasure_DistCircle) {
11277 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11278 powf((float)(r_rband.y - lastPoint.y), 2));
11279
11280 dc.SetPen(*g_pRouteMan->GetRoutePen());
11281 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11282 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11283 }
11284 }
11285 }
11286 }
11287
11288 wxString routeInfo;
11289 double varBrg = 0;
11290 if (g_bShowTrue)
11291 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11292 0x00B0);
11293
11294 if (g_bShowMag) {
11295 double latAverage = (m_cursor_lat + render_lat) / 2;
11296 double lonAverage = (m_cursor_lon + render_lon) / 2;
11297 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11298
11299 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11300 (int)varBrg, 0x00B0);
11301 }
11302 routeInfo << _T(" ") << FormatDistanceAdaptive(dist);
11303
11304 // To make it easier to use a route as a bearing on a charted object add for
11305 // the first leg also the reverse bearing.
11306 if (np == 1) {
11307 routeInfo << "\nReverse: ";
11308 if (g_bShowTrue)
11309 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11310 (int)(brg + 180.) % 360, 0x00B0);
11311 if (g_bShowMag)
11312 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11313 (int)(varBrg + 180.) % 360, 0x00B0);
11314 }
11315
11316 wxString s0;
11317 if (!route->m_bIsInLayer)
11318 s0.Append(_("Route") + _T(": "));
11319 else
11320 s0.Append(_("Layer Route: "));
11321
11322 double disp_length = route->m_route_length;
11323 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11324 s0 += FormatDistanceAdaptive(disp_length);
11325
11326 RouteLegInfo(dc, r_rband, routeInfo, s0);
11327
11328 m_brepaint_piano = true;
11329}
11330
11331void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11332 if (!m_bShowVisibleSectors) return;
11333
11334 if (g_bDeferredInitDone) {
11335 // need to re-evaluate sectors?
11336 double rhumbBearing, rhumbDist;
11337 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11338 &rhumbBearing, &rhumbDist);
11339
11340 if (rhumbDist > 0.05) // miles
11341 {
11342 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11343 m_sectorlegsVisible);
11344 m_sector_glat = gLat;
11345 m_sector_glon = gLon;
11346 }
11347 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11348 }
11349}
11350
11351void ChartCanvas::WarpPointerDeferred(int x, int y) {
11352 warp_x = x;
11353 warp_y = y;
11354 warp_flag = true;
11355}
11356
11357int s_msg;
11358
11359void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11360 if (!ps52plib) return;
11361
11362 if (VPoint.b_quilt) { // quilted
11363 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11364
11365 if (m_pQuilt->IsQuiltVector()) {
11366 if (ps52plib->GetStateHash() != m_s52StateHash) {
11367 UpdateS52State();
11368 m_s52StateHash = ps52plib->GetStateHash();
11369 }
11370 }
11371 } else {
11372 if (ps52plib->GetStateHash() != m_s52StateHash) {
11373 UpdateS52State();
11374 m_s52StateHash = ps52plib->GetStateHash();
11375 }
11376 }
11377
11378 // Plugin charts
11379 bool bSendPlibState = true;
11380 if (VPoint.b_quilt) { // quilted
11381 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11382 }
11383
11384 if (bSendPlibState) {
11385 wxJSONValue v;
11386 v[_T("OpenCPN Version Major")] = VERSION_MAJOR;
11387 v[_T("OpenCPN Version Minor")] = VERSION_MINOR;
11388 v[_T("OpenCPN Version Patch")] = VERSION_PATCH;
11389 v[_T("OpenCPN Version Date")] = VERSION_DATE;
11390 v[_T("OpenCPN Version Full")] = VERSION_FULL;
11391
11392 // S52PLIB state
11393 v[_T("OpenCPN S52PLIB ShowText")] = GetShowENCText();
11394 v[_T("OpenCPN S52PLIB ShowSoundings")] = GetShowENCDepth();
11395 v[_T("OpenCPN S52PLIB ShowLights")] = GetShowENCLights();
11396 v[_T("OpenCPN S52PLIB ShowAnchorConditions")] = m_encShowAnchor;
11397 v[_T("OpenCPN S52PLIB ShowQualityOfData")] = GetShowENCDataQual();
11398 v[_T("OpenCPN S52PLIB ShowATONLabel")] = GetShowENCBuoyLabels();
11399 v[_T("OpenCPN S52PLIB ShowLightDescription")] = GetShowENCLightDesc();
11400
11401 v[_T("OpenCPN S52PLIB DisplayCategory")] = GetENCDisplayCategory();
11402
11403 v[_T("OpenCPN S52PLIB SoundingsFactor")] = g_ENCSoundingScaleFactor;
11404 v[_T("OpenCPN S52PLIB TextFactor")] = g_ENCTextScaleFactor;
11405
11406 // Global S52 options
11407
11408 v[_T("OpenCPN S52PLIB MetaDisplay")] = ps52plib->m_bShowMeta;
11409 v[_T("OpenCPN S52PLIB DeclutterText")] = ps52plib->m_bDeClutterText;
11410 v[_T("OpenCPN S52PLIB ShowNationalText")] = ps52plib->m_bShowNationalTexts;
11411 v[_T("OpenCPN S52PLIB ShowImportantTextOnly")] =
11412 ps52plib->m_bShowS57ImportantTextOnly;
11413 v[_T("OpenCPN S52PLIB UseSCAMIN")] = ps52plib->m_bUseSCAMIN;
11414 v[_T("OpenCPN S52PLIB UseSUPER_SCAMIN")] = ps52plib->m_bUseSUPER_SCAMIN;
11415 v[_T("OpenCPN S52PLIB SymbolStyle")] = ps52plib->m_nSymbolStyle;
11416 v[_T("OpenCPN S52PLIB BoundaryStyle")] = ps52plib->m_nBoundaryStyle;
11417 v[_T("OpenCPN S52PLIB ColorShades")] =
11418 S52_getMarinerParam(S52_MAR_TWO_SHADES);
11419
11420 // Some global GUI parameters, for completeness
11421 v[_T("OpenCPN Zoom Mod Vector")] = g_chart_zoom_modifier_vector;
11422 v[_T("OpenCPN Zoom Mod Other")] = g_chart_zoom_modifier_raster;
11423 v[_T("OpenCPN Scale Factor Exp")] =
11424 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11425 v[_T("OpenCPN Display Width")] = (int)g_display_size_mm;
11426
11427 wxJSONWriter w;
11428 wxString out;
11429 w.Write(v, out);
11430
11431 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11432 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11433 g_lastS52PLIBPluginMessage = out;
11434 }
11435 }
11436}
11437int spaint;
11438int s_in_update;
11439void ChartCanvas::OnPaint(wxPaintEvent &event) {
11440 wxPaintDC dc(this);
11441
11442 // GetToolbar()->Show( m_bToolbarEnable );
11443
11444 // Paint updates may have been externally disabled (temporarily, to avoid
11445 // Yield() recursion performance loss) It is important that the wxPaintDC is
11446 // built, even if we elect to not process this paint message. Otherwise, the
11447 // paint message may not be removed from the message queue, esp on Windows.
11448 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11449
11450 if (!m_b_paint_enable) {
11451 return;
11452 }
11453
11454 // If necessary, reconfigure the S52 PLIB
11455 UpdateCanvasS52PLIBConfig();
11456
11457#ifdef ocpnUSE_GL
11458 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11459
11460 if (m_glcc && g_bopengl) {
11461 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11462 s_in_update++;
11463 m_glcc->Update();
11464 s_in_update--;
11465 }
11466
11467 return;
11468 }
11469#endif
11470
11471 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11472
11473 wxRegion ru = GetUpdateRegion();
11474
11475 int rx, ry, rwidth, rheight;
11476 ru.GetBox(rx, ry, rwidth, rheight);
11477 // printf("%d Onpaint update region box: %d %d %d %d\n", spaint++, rx, ry,
11478 // rwidth, rheight);
11479
11480#ifdef ocpnUSE_DIBSECTION
11481 ocpnMemDC temp_dc;
11482#else
11483 wxMemoryDC temp_dc;
11484#endif
11485
11486 long height = GetVP().pix_height;
11487
11488#ifdef __WXMAC__
11489 // On OS X we have to explicitly extend the region for the piano area
11490 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11491 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11492 height += m_Piano->GetHeight();
11493#endif // __WXMAC__
11494 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11495
11496 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11497 if (pthumbwin) {
11498 int thumbx, thumby, thumbsx, thumbsy;
11499 pthumbwin->GetPosition(&thumbx, &thumby);
11500 pthumbwin->GetSize(&thumbsx, &thumbsy);
11501 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11502
11503 if (pthumbwin->IsShown()) {
11504 rgn_chart.Subtract(rgn_thumbwin);
11505 ru.Subtract(rgn_thumbwin);
11506 }
11507 }
11508
11509 // subtract the chart bar if it isn't transparent, and determine if we need to
11510 // paint it
11511 wxRegion rgn_blit = ru;
11512 if (g_bShowChartBar) {
11513 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11514 GetClientSize().x, m_Piano->GetHeight());
11515
11516 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11517 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11518 if (style->chartStatusWindowTransparent)
11519 m_brepaint_piano = true;
11520 else
11521 ru.Subtract(chart_bar_rect);
11522 }
11523 }
11524
11525 if (m_Compass && m_Compass->IsShown()) {
11526 wxRect compassRect = m_Compass->GetRect();
11527 if (ru.Contains(compassRect) != wxOutRegion) {
11528 ru.Subtract(compassRect);
11529 }
11530 }
11531
11532 wxRect noteRect = m_notification_button->GetRect();
11533 if (ru.Contains(noteRect) != wxOutRegion) {
11534 ru.Subtract(noteRect);
11535 }
11536
11537 // Is this viewpoint the same as the previously painted one?
11538 bool b_newview = true;
11539
11540 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11541 (m_cache_vp.rotation == VPoint.rotation) &&
11542 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11543 m_cache_vp.IsValid()) {
11544 b_newview = false;
11545 }
11546
11547 // If the ViewPort is skewed or rotated, we may be able to use the cached
11548 // rotated bitmap.
11549 bool b_rcache_ok = false;
11550 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11551 b_rcache_ok = !b_newview;
11552
11553 // Make a special VP
11554 if (VPoint.b_MercatorProjectionOverride)
11555 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11556 ViewPort svp = VPoint;
11557
11558 svp.pix_width = svp.rv_rect.width;
11559 svp.pix_height = svp.rv_rect.height;
11560
11561 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11562 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11563 // VPoint.rv_rect.height);
11564
11565 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11566
11567 // If we are going to use the cached rotated image, there is no need to fetch
11568 // any chart data and this will do it...
11569 if (b_rcache_ok) chart_get_region.Clear();
11570
11571 // Blit pan acceleration
11572 if (VPoint.b_quilt) // quilted
11573 {
11574 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11575
11576 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11577
11578 bool busy = false;
11579 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11580 m_cache_vp.rotation != VPoint.rotation)) {
11581 AbstractPlatform::ShowBusySpinner();
11582 busy = true;
11583 }
11584
11585 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11586 (m_working_bm.GetHeight() != svp.pix_height))
11587 m_working_bm.Create(svp.pix_width, svp.pix_height,
11588 -1); // make sure the target is big enoug
11589
11590 if (fabs(VPoint.rotation) < 0.01) {
11591 bool b_save = true;
11592
11593 if (g_SencThreadManager) {
11594 if (g_SencThreadManager->GetJobCount()) {
11595 b_save = false;
11596 m_cache_vp.Invalidate();
11597 }
11598 }
11599
11600 // If the saved wxBitmap from last OnPaint is useable
11601 // calculate the blit parameters
11602
11603 // We can only do screen blit painting if subsequent ViewPorts differ by
11604 // whole pixels So, in small scale bFollow mode, force the full screen
11605 // render. This seems a hack....There may be better logic here.....
11606
11607 // if(m_bFollow)
11608 // b_save = false;
11609
11610 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11611 if (b_newview) {
11612 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11613 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11614
11615 int dy = c_new.y - c_old.y;
11616 int dx = c_new.x - c_old.x;
11617
11618 // printf("In OnPaint Trying Blit dx: %d
11619 // dy:%d\n\n", dx, dy);
11620
11621 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11622 if (dx || dy) {
11623 // Blit the reuseable portion of the cached wxBitmap to a working
11624 // bitmap
11625 temp_dc.SelectObject(m_working_bm);
11626
11627 wxMemoryDC cache_dc;
11628 cache_dc.SelectObject(m_cached_chart_bm);
11629
11630 if (dy > 0) {
11631 if (dx > 0) {
11632 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11633 VPoint.pix_height - dy, &cache_dc, dx, dy);
11634 } else {
11635 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11636 VPoint.pix_height - dy, &cache_dc, 0, dy);
11637 }
11638
11639 } else {
11640 if (dx > 0) {
11641 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11642 VPoint.pix_height + dy, &cache_dc, dx, 0);
11643 } else {
11644 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11645 VPoint.pix_height + dy, &cache_dc, 0, 0);
11646 }
11647 }
11648
11649 OCPNRegion update_region;
11650 if (dy) {
11651 if (dy > 0)
11652 update_region.Union(
11653 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11654 else
11655 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11656 }
11657
11658 if (dx) {
11659 if (dx > 0)
11660 update_region.Union(
11661 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11662 else
11663 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11664 }
11665
11666 // Render the new region
11667 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11668 update_region);
11669 cache_dc.SelectObject(wxNullBitmap);
11670 } else {
11671 // No sensible (dx, dy) change in the view, so use the cached
11672 // member bitmap
11673 temp_dc.SelectObject(m_cached_chart_bm);
11674 b_save = false;
11675 }
11676 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11677
11678 } else // not blitable
11679 {
11680 temp_dc.SelectObject(m_working_bm);
11681 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11682 chart_get_region);
11683 }
11684 } else {
11685 // No change in the view, so use the cached member bitmap2
11686 temp_dc.SelectObject(m_cached_chart_bm);
11687 b_save = false;
11688 }
11689 } else // cached bitmap is not yet valid
11690 {
11691 temp_dc.SelectObject(m_working_bm);
11692 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11693 chart_get_region);
11694 }
11695
11696 // Save the fully rendered quilt image as a wxBitmap member of this class
11697 if (b_save) {
11698 // if((m_cached_chart_bm.GetWidth() !=
11699 // svp.pix_width) ||
11700 // (m_cached_chart_bm.GetHeight() !=
11701 // svp.pix_height))
11702 // m_cached_chart_bm.Create(svp.pix_width,
11703 // svp.pix_height, -1); // target wxBitmap
11704 // is big enough
11705 wxMemoryDC scratch_dc_0;
11706 scratch_dc_0.SelectObject(m_cached_chart_bm);
11707 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11708
11709 scratch_dc_0.SelectObject(wxNullBitmap);
11710
11711 m_bm_cache_vp =
11712 VPoint; // save the ViewPort associated with the cached wxBitmap
11713 }
11714 }
11715
11716 else // quilted, rotated
11717 {
11718 temp_dc.SelectObject(m_working_bm);
11719 OCPNRegion chart_get_all_region(
11720 wxRect(0, 0, svp.pix_width, svp.pix_height));
11721 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11722 chart_get_all_region);
11723 }
11724
11725 AbstractPlatform::HideBusySpinner();
11726
11727 }
11728
11729 else // not quilted
11730 {
11731 if (!m_singleChart) {
11732 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
11733 dc.Clear();
11734 return;
11735 }
11736
11737 if (!chart_get_region.IsEmpty()) {
11738 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
11739 }
11740 }
11741
11742 if (temp_dc.IsOk()) {
11743 // Arrange to render the World Chart vector data behind the rendered
11744 // current chart so that uncovered canvas areas show at least the world
11745 // chart.
11746 OCPNRegion chartValidRegion;
11747 if (!VPoint.b_quilt) {
11748 // Make a region covering the current chart on the canvas
11749
11750 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
11751 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
11752 else {
11753 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
11754 // require that the viewport passed here have pix_width and pix_height
11755 // set to the actual display, not the virtual (rv_rect) sizes
11756 // (the vector calculations require the virtual sizes in svp)
11757
11758 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
11759 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
11760 }
11761 } else
11762 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
11763
11764 temp_dc.DestroyClippingRegion();
11765
11766 // Copy current chart region
11767 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
11768
11769 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
11770
11771 if (!backgroundRegion.IsEmpty()) {
11772 // Draw the Background Chart only in the areas NOT covered by the
11773 // current chart view
11774
11775 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
11776 clipping regions with more than 1 rectangle so... */
11777 wxColour water = pWorldBackgroundChart->water;
11778 if (water.IsOk()) {
11779 temp_dc.SetPen(*wxTRANSPARENT_PEN);
11780 temp_dc.SetBrush(wxBrush(water));
11781 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
11782 while (upd.HaveRects()) {
11783 wxRect rect = upd.GetRect();
11784 temp_dc.DrawRectangle(rect);
11785 upd.NextRect();
11786 }
11787 }
11788 // Associate with temp_dc
11789 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
11790 temp_dc.SetDeviceClippingRegion(*clip_region);
11791 delete clip_region;
11792
11793 ocpnDC bgdc(temp_dc);
11794 double r = VPoint.rotation;
11795 SetVPRotation(VPoint.skew);
11796
11797 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
11798 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
11799
11800 SetVPRotation(r);
11801 }
11802 } // temp_dc.IsOk();
11803
11804 wxMemoryDC *pChartDC = &temp_dc;
11805 wxMemoryDC rotd_dc;
11806
11807 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
11808 // Can we use the current rotated image cache?
11809 if (!b_rcache_ok) {
11810#ifdef __WXMSW__
11811 wxMemoryDC tbase_dc;
11812 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
11813 tbase_dc.SelectObject(bm_base);
11814 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11815 tbase_dc.SelectObject(wxNullBitmap);
11816#else
11817 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
11818#endif
11819
11820 wxImage base_image;
11821 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
11822
11823 // Use a local static image rotator to improve wxWidgets code profile
11824 // Especially, on GTK the wxRound and wxRealPoint functions are very
11825 // expensive.....
11826
11827 double angle = GetVP().skew - GetVP().rotation;
11828 wxImage ri;
11829 bool b_rot_ok = false;
11830 if (base_image.IsOk()) {
11831 ViewPort rot_vp = GetVP();
11832
11833 m_b_rot_hidef = false;
11834
11835 ri = Image_Rotate(
11836 base_image, angle,
11837 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
11838 m_b_rot_hidef, &m_roffset);
11839
11840 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11841 (rot_vp.rotation == VPoint.rotation) &&
11842 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
11843 rot_vp.IsValid() && (ri.IsOk())) {
11844 b_rot_ok = true;
11845 }
11846 }
11847
11848 if (b_rot_ok) {
11849 delete m_prot_bm;
11850 m_prot_bm = new wxBitmap(ri);
11851 }
11852
11853 m_roffset.x += VPoint.rv_rect.x;
11854 m_roffset.y += VPoint.rv_rect.y;
11855 }
11856
11857 if (m_prot_bm && m_prot_bm->IsOk()) {
11858 rotd_dc.SelectObject(*m_prot_bm);
11859 pChartDC = &rotd_dc;
11860 } else {
11861 pChartDC = &temp_dc;
11862 m_roffset = wxPoint(0, 0);
11863 }
11864 } else { // unrotated
11865 pChartDC = &temp_dc;
11866 m_roffset = wxPoint(0, 0);
11867 }
11868
11869 wxPoint offset = m_roffset;
11870
11871 // Save the PixelCache viewpoint for next time
11872 m_cache_vp = VPoint;
11873
11874 // Set up a scratch DC for overlay objects
11875 wxMemoryDC mscratch_dc;
11876 mscratch_dc.SelectObject(*pscratch_bm);
11877
11878 mscratch_dc.ResetBoundingBox();
11879 mscratch_dc.DestroyClippingRegion();
11880 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
11881
11882 // Blit the externally invalidated areas of the chart onto the scratch dc
11883 wxRegionIterator upd(rgn_blit); // get the update rect list
11884 while (upd) {
11885 wxRect rect = upd.GetRect();
11886
11887 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
11888 rect.x - offset.x, rect.y - offset.y);
11889 upd++;
11890 }
11891
11892 // If multi-canvas, indicate which canvas has keyboard focus
11893 // by drawing a simple blue bar at the top.
11894 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
11895 if (this == wxWindow::FindFocus()) {
11896 g_focusCanvas = this;
11897
11898 wxColour colour = GetGlobalColor(_T("BLUE4"));
11899 mscratch_dc.SetPen(wxPen(colour));
11900 mscratch_dc.SetBrush(wxBrush(colour));
11901
11902 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
11903 mscratch_dc.DrawRectangle(activeRect);
11904 }
11905 }
11906
11907 // Any MBtiles?
11908 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
11909 unsigned int im = stackIndexArray.size();
11910 if (VPoint.b_quilt && im > 0) {
11911 std::vector<int> tiles_to_show;
11912 for (unsigned int is = 0; is < im; is++) {
11913 const ChartTableEntry &cte =
11914 ChartData->GetChartTableEntry(stackIndexArray[is]);
11915 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
11916 continue;
11917 }
11918 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
11919 tiles_to_show.push_back(stackIndexArray[is]);
11920 }
11921 }
11922
11923 if (tiles_to_show.size())
11924 SetAlertString(_("MBTile requires OpenGL to be enabled"));
11925 }
11926
11927 // May get an unexpected OnPaint call while switching display modes
11928 // Guard for that.
11929 if (!g_bopengl) {
11930 ocpnDC scratch_dc(mscratch_dc);
11931 RenderAlertMessage(mscratch_dc, GetVP());
11932 }
11933
11934#if 0
11935 // quiting?
11936 if (g_bquiting) {
11937#ifdef ocpnUSE_DIBSECTION
11938 ocpnMemDC q_dc;
11939#else
11940 wxMemoryDC q_dc;
11941#endif
11942 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
11943 q_dc.SelectObject(qbm);
11944
11945 // Get a copy of the screen
11946 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
11947
11948 // Draw a rectangle over the screen with a stipple brush
11949 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
11950 q_dc.SetBrush(qbr);
11951 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
11952
11953 // Blit back into source
11954 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
11955 wxCOPY);
11956
11957 q_dc.SelectObject(wxNullBitmap);
11958 }
11959#endif
11960
11961#if 0
11962 // It is possible that this two-step method may be reuired for some platforms.
11963 // So, retain in the code base to aid recovery if necessary
11964
11965 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
11966 if( VPoint.b_quilt ) {
11967 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
11968 ChartBase *chart = m_pQuilt->GetRefChart();
11969 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
11970
11971 // Clear the text Global declutter list
11972 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
11973 if(ChPI)
11974 ChPI->ClearPLIBTextList();
11975 else{
11976 if(ps52plib)
11977 ps52plib->ClearTextList();
11978 }
11979
11980 wxMemoryDC t_dc;
11981 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
11982
11983 wxColor maskBackground = wxColour(1,0,0);
11984 t_dc.SelectObject( qbm );
11985 t_dc.SetBackground(wxBrush(maskBackground));
11986 t_dc.Clear();
11987
11988 // Copy the scratch DC into the new bitmap
11989 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
11990
11991 // Render the text to the new bitmap
11992 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
11993 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
11994
11995 // Copy the new bitmap back to the scratch dc
11996 wxRegionIterator upd_final( ru );
11997 while( upd_final ) {
11998 wxRect rect = upd_final.GetRect();
11999 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12000 upd_final++;
12001 }
12002
12003 t_dc.SelectObject( wxNullBitmap );
12004 }
12005 }
12006 }
12007#endif
12008 // Direct rendering model...
12009 if (VPoint.b_quilt) {
12010 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12011 ChartBase *chart = m_pQuilt->GetRefChart();
12012 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12013 // Clear the text Global declutter list
12014 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12015 if (ChPI)
12016 ChPI->ClearPLIBTextList();
12017 else {
12018 if (ps52plib) ps52plib->ClearTextList();
12019 }
12020
12021 // Render the text directly to the scratch bitmap
12022 OCPNRegion chart_all_text_region(
12023 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12024
12025 if (g_bShowChartBar && m_Piano) {
12026 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12027 GetVP().pix_width, m_Piano->GetHeight());
12028
12029 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12030 if (!style->chartStatusWindowTransparent)
12031 chart_all_text_region.Subtract(chart_bar_rect);
12032 }
12033
12034 if (m_Compass && m_Compass->IsShown()) {
12035 wxRect compassRect = m_Compass->GetRect();
12036 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12037 chart_all_text_region.Subtract(compassRect);
12038 }
12039 }
12040
12041 mscratch_dc.DestroyClippingRegion();
12042
12043 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12044 chart_all_text_region);
12045 }
12046 }
12047 }
12048
12049 // Now that charts are fully rendered, apply the overlay objects as decals.
12050 ocpnDC scratch_dc(mscratch_dc);
12051 DrawOverlayObjects(scratch_dc, ru);
12052
12053 // And finally, blit the scratch dc onto the physical dc
12054 wxRegionIterator upd_final(rgn_blit);
12055 while (upd_final) {
12056 wxRect rect = upd_final.GetRect();
12057 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12058 rect.y);
12059 upd_final++;
12060 }
12061
12062 // Deselect the chart bitmap from the temp_dc, so that it will not be
12063 // destroyed in the temp_dc dtor
12064 temp_dc.SelectObject(wxNullBitmap);
12065 // And for the scratch bitmap
12066 mscratch_dc.SelectObject(wxNullBitmap);
12067
12068 dc.DestroyClippingRegion();
12069
12070 PaintCleanup();
12071}
12072
12073void ChartCanvas::PaintCleanup() {
12074 // Handle the current graphic window, if present
12075
12076 if (pCwin) {
12077 pCwin->Show();
12078 if (m_bTCupdate) {
12079 pCwin->Refresh();
12080 pCwin->Update();
12081 }
12082 }
12083
12084 // And set flags for next time
12085 m_bTCupdate = false;
12086
12087 // Handle deferred WarpPointer
12088 if (warp_flag) {
12089 WarpPointer(warp_x, warp_y);
12090 warp_flag = false;
12091 }
12092
12093 // Start movement timers, this runs nearly immediately.
12094 // the reason we cannot simply call it directly is the
12095 // refresh events it emits may be blocked from this paint event
12096 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12097 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12098}
12099
12100#if 0
12101wxColour GetErrorGraphicColor(double val)
12102{
12103 /*
12104 double valm = wxMin(val_max, val);
12105
12106 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12107 unsigned char red = (unsigned char)(255 * (valm/val_max));
12108
12109 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12110
12111 hv.saturation = 1.0;
12112 hv.value = 1.0;
12113
12114 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12115 return wxColour(rv.red, rv.green, rv.blue);
12116 */
12117
12118 // HTML colors taken from NOAA WW3 Web representation
12119 wxColour c;
12120 if((val > 0) && (val < 1)) c.Set(_T("#002ad9"));
12121 else if((val >= 1) && (val < 2)) c.Set(_T("#006ed9"));
12122 else if((val >= 2) && (val < 3)) c.Set(_T("#00b2d9"));
12123 else if((val >= 3) && (val < 4)) c.Set(_T("#00d4d4"));
12124 else if((val >= 4) && (val < 5)) c.Set(_T("#00d9a6"));
12125 else if((val >= 5) && (val < 7)) c.Set(_T("#00d900"));
12126 else if((val >= 7) && (val < 9)) c.Set(_T("#95d900"));
12127 else if((val >= 9) && (val < 12)) c.Set(_T("#d9d900"));
12128 else if((val >= 12) && (val < 15)) c.Set(_T("#d9ae00"));
12129 else if((val >= 15) && (val < 18)) c.Set(_T("#d98300"));
12130 else if((val >= 18) && (val < 21)) c.Set(_T("#d95700"));
12131 else if((val >= 21) && (val < 24)) c.Set(_T("#d90000"));
12132 else if((val >= 24) && (val < 27)) c.Set(_T("#ae0000"));
12133 else if((val >= 27) && (val < 30)) c.Set(_T("#8c0000"));
12134 else if((val >= 30) && (val < 36)) c.Set(_T("#870000"));
12135 else if((val >= 36) && (val < 42)) c.Set(_T("#690000"));
12136 else if((val >= 42) && (val < 48)) c.Set(_T("#550000"));
12137 else if( val >= 48) c.Set(_T("#410000"));
12138
12139 return c;
12140}
12141
12142void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12143{
12144 wxImage gr_image(vp->pix_width, vp->pix_height);
12145 gr_image.InitAlpha();
12146
12147 double maxval = -10000;
12148 double minval = 10000;
12149
12150 double rlat, rlon;
12151 double glat, glon;
12152
12153 GetCanvasPixPoint(0, 0, rlat, rlon);
12154
12155 for(int i=1; i < vp->pix_height-1; i++)
12156 {
12157 for(int j=0; j < vp->pix_width; j++)
12158 {
12159 // Reference mercator value
12160// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12161
12162 // Georef value
12163 GetCanvasPixPoint(j, i, glat, glon);
12164
12165 maxval = wxMax(maxval, (glat - rlat));
12166 minval = wxMin(minval, (glat - rlat));
12167
12168 }
12169 rlat = glat;
12170 }
12171
12172 GetCanvasPixPoint(0, 0, rlat, rlon);
12173 for(int i=1; i < vp->pix_height-1; i++)
12174 {
12175 for(int j=0; j < vp->pix_width; j++)
12176 {
12177 // Reference mercator value
12178// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12179
12180 // Georef value
12181 GetCanvasPixPoint(j, i, glat, glon);
12182
12183 double f = ((glat - rlat)-minval)/(maxval - minval);
12184
12185 double dy = (f * 40);
12186
12187 wxColour c = GetErrorGraphicColor(dy);
12188 unsigned char r = c.Red();
12189 unsigned char g = c.Green();
12190 unsigned char b = c.Blue();
12191
12192 gr_image.SetRGB(j, i, r,g,b);
12193 if((glat - rlat )!= 0)
12194 gr_image.SetAlpha(j, i, 128);
12195 else
12196 gr_image.SetAlpha(j, i, 255);
12197
12198 }
12199 rlat = glat;
12200 }
12201
12202 // Create a Bitmap
12203 wxBitmap *pbm = new wxBitmap(gr_image);
12204 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12205 pbm->SetMask(gr_mask);
12206
12207 pmdc->DrawBitmap(*pbm, 0,0);
12208
12209 delete pbm;
12210
12211}
12212
12213#endif
12214
12215void ChartCanvas::CancelMouseRoute() {
12216 m_routeState = 0;
12217 m_pMouseRoute = NULL;
12218 m_bDrawingRoute = false;
12219}
12220
12221int ChartCanvas::GetNextContextMenuId() {
12222 return CanvasMenuHandler::GetNextContextMenuId();
12223}
12224
12225bool ChartCanvas::SetCursor(const wxCursor &c) {
12226#ifdef ocpnUSE_GL
12227 if (g_bopengl && m_glcc)
12228 return m_glcc->SetCursor(c);
12229 else
12230#endif
12231 return wxWindow::SetCursor(c);
12232}
12233
12234void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12235 if (g_bquiting) return;
12236 // Keep the mouse position members up to date
12237 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12238
12239 // Retrigger the route leg popup timer
12240 // This handles the case when the chart is moving in auto-follow mode,
12241 // but no user mouse input is made. The timer handler may Hide() the
12242 // popup if the chart moved enough n.b. We use slightly longer oneshot
12243 // value to allow this method's Refresh() to complete before potentially
12244 // getting another Refresh() in the popup timer handler.
12245 if (!m_RolloverPopupTimer.IsRunning() &&
12246 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12247 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12248 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12249 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12250
12251#ifdef ocpnUSE_GL
12252 if (m_glcc && g_bopengl) {
12253 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12254 // overlay objects.
12255 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12256
12257 m_glcc->Refresh(eraseBackground,
12258 NULL); // We always are going to render the entire screen
12259 // anyway, so make
12260 // sure that the window managers understand the invalid area
12261 // is actually the entire client area.
12262
12263 // We need to selectively Refresh some child windows, if they are visible.
12264 // Note that some children are refreshed elsewhere on timer ticks, so don't
12265 // need attention here.
12266
12267 // Thumbnail chart
12268 if (pthumbwin && pthumbwin->IsShown()) {
12269 pthumbwin->Raise();
12270 pthumbwin->Refresh(false);
12271 }
12272
12273 // ChartInfo window
12274 if (m_pCIWin && m_pCIWin->IsShown()) {
12275 m_pCIWin->Raise();
12276 m_pCIWin->Refresh(false);
12277 }
12278
12279 // if(g_MainToolbar)
12280 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12281
12282 } else
12283#endif
12284 wxWindow::Refresh(eraseBackground, rect);
12285}
12286
12287void ChartCanvas::Update() {
12288 if (m_glcc && g_bopengl) {
12289#ifdef ocpnUSE_GL
12290 m_glcc->Update();
12291#endif
12292 } else
12293 wxWindow::Update();
12294}
12295
12296void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12297 if (!pemboss) return;
12298 int x = pemboss->x, y = pemboss->y;
12299 const double factor = 200;
12300
12301 wxASSERT_MSG(dc.GetDC(), wxT("DrawEmboss has no dc (opengl?)"));
12302 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12303 wxASSERT_MSG(pmdc, wxT("dc to EmbossCanvas not a memory dc"));
12304
12305 // Grab a snipped image out of the chart
12306 wxMemoryDC snip_dc;
12307 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12308 snip_dc.SelectObject(snip_bmp);
12309
12310 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12311 snip_dc.SelectObject(wxNullBitmap);
12312
12313 wxImage snip_img = snip_bmp.ConvertToImage();
12314
12315 // Apply Emboss map to the snip image
12316 unsigned char *pdata = snip_img.GetData();
12317 if (pdata) {
12318 for (int y = 0; y < pemboss->height; y++) {
12319 int map_index = (y * pemboss->width);
12320 for (int x = 0; x < pemboss->width; x++) {
12321 double val = (pemboss->pmap[map_index] * factor) / 256.;
12322
12323 int nred = (int)((*pdata) + val);
12324 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12325 *pdata++ = (unsigned char)nred;
12326
12327 int ngreen = (int)((*pdata) + val);
12328 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12329 *pdata++ = (unsigned char)ngreen;
12330
12331 int nblue = (int)((*pdata) + val);
12332 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12333 *pdata++ = (unsigned char)nblue;
12334
12335 map_index++;
12336 }
12337 }
12338 }
12339
12340 // Convert embossed snip to a bitmap
12341 wxBitmap emb_bmp(snip_img);
12342
12343 // Map to another memoryDC
12344 wxMemoryDC result_dc;
12345 result_dc.SelectObject(emb_bmp);
12346
12347 // Blit to target
12348 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12349
12350 result_dc.SelectObject(wxNullBitmap);
12351}
12352
12353emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12354 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12355
12356 if (GetQuiltMode()) {
12357 // disable Overzoom indicator for MBTiles
12358 int refIndex = GetQuiltRefChartdbIndex();
12359 if (refIndex >= 0) {
12360 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12361 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12362 if (current_type == CHART_TYPE_MBTILES) {
12363 ChartBase *pChart = m_pQuilt->GetRefChart();
12364 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12365 if (ptc) {
12366 zoom_factor = ptc->GetZoomFactor();
12367 }
12368 }
12369 }
12370
12371 if (zoom_factor <= 3.9) return NULL;
12372 } else {
12373 if (m_singleChart) {
12374 if (zoom_factor <= 3.9) return NULL;
12375 } else
12376 return NULL;
12377 }
12378
12379 if (m_pEM_OverZoom) {
12380 m_pEM_OverZoom->x = 4;
12381 m_pEM_OverZoom->y = 0;
12382 if (g_MainToolbar && IsPrimaryCanvas()) {
12383 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12384 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12385 }
12386 }
12387 return m_pEM_OverZoom;
12388}
12389
12390void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12391 GridDraw(dc);
12392
12393 // bool pluginOverlayRender = true;
12394 //
12395 // if(g_canvasConfig > 0){ // Multi canvas
12396 // if(IsPrimaryCanvas())
12397 // pluginOverlayRender = false;
12398 // }
12399
12400 g_overlayCanvas = this;
12401
12402 if (g_pi_manager) {
12403 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12404 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12405 OVERLAY_LEGACY);
12406 }
12407
12408 AISDrawAreaNotices(dc, GetVP(), this);
12409
12410 wxDC *pdc = dc.GetDC();
12411 if (pdc) {
12412 pdc->DestroyClippingRegion();
12413 wxDCClipper(*pdc, ru);
12414 }
12415
12416 if (m_bShowNavobjects) {
12417 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12418 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12419 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12420 DrawAnchorWatchPoints(dc);
12421 } else {
12422 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12423 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12424 }
12425
12426 AISDraw(dc, GetVP(), this);
12427 ShipDraw(dc);
12428 AlertDraw(dc);
12429
12430 RenderVisibleSectorLights(dc);
12431
12432 RenderAllChartOutlines(dc, GetVP());
12433 RenderRouteLegs(dc);
12434 RenderShipToActive(dc, false);
12435 ScaleBarDraw(dc);
12436 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12437 if (g_pi_manager) {
12438 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12439 OVERLAY_OVER_SHIPS);
12440 }
12441
12442 DrawEmboss(dc, EmbossDepthScale());
12443 DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12444 if (g_pi_manager) {
12445 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12446 OVERLAY_OVER_EMBOSS);
12447 }
12448
12449 if (m_bShowTide) {
12450 RebuildTideSelectList(GetVP().GetBBox());
12451 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12452 }
12453
12454 if (m_bShowCurrent) {
12455 RebuildCurrentSelectList(GetVP().GetBBox());
12456 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12457 }
12458
12459 if (!g_PrintingInProgress) {
12460 if (IsPrimaryCanvas()) {
12461 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12462 }
12463
12464 if (IsPrimaryCanvas()) {
12465 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12466 }
12467
12468 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12469
12470 if (m_pTrackRolloverWin) {
12471 m_pTrackRolloverWin->Draw(dc);
12472 m_brepaint_piano = true;
12473 }
12474
12475 if (m_pRouteRolloverWin) {
12476 m_pRouteRolloverWin->Draw(dc);
12477 m_brepaint_piano = true;
12478 }
12479
12480 if (m_pAISRolloverWin) {
12481 m_pAISRolloverWin->Draw(dc);
12482 m_brepaint_piano = true;
12483 }
12484 if (m_brepaint_piano && g_bShowChartBar) {
12485 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12486 }
12487
12488 if (m_Compass) m_Compass->Paint(dc);
12489
12490 if (!g_CanvasHideNotificationIcon) {
12491 auto &noteman = NotificationManager::GetInstance();
12492 if (noteman.GetNotificationCount()) {
12493 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12494 if (m_notification_button->UpdateStatus()) Refresh();
12495 m_notification_button->Show(true);
12496 m_notification_button->Paint(dc);
12497 } else {
12498 m_notification_button->Show(false);
12499 }
12500 }
12501 }
12502 if (g_pi_manager) {
12503 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12504 OVERLAY_OVER_UI);
12505 }
12506}
12507
12508emboss_data *ChartCanvas::EmbossDepthScale() {
12509 if (!m_bShowDepthUnits) return NULL;
12510
12511 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12512
12513 if (GetQuiltMode()) {
12514 wxString s = m_pQuilt->GetQuiltDepthUnit();
12515 s.MakeUpper();
12516 if (s == _T("FEET"))
12517 depth_unit_type = DEPTH_UNIT_FEET;
12518 else if (s.StartsWith(_T("FATHOMS")))
12519 depth_unit_type = DEPTH_UNIT_FATHOMS;
12520 else if (s.StartsWith(_T("METERS")))
12521 depth_unit_type = DEPTH_UNIT_METERS;
12522 else if (s.StartsWith(_T("METRES")))
12523 depth_unit_type = DEPTH_UNIT_METERS;
12524 else if (s.StartsWith(_T("METRIC")))
12525 depth_unit_type = DEPTH_UNIT_METERS;
12526 else if (s.StartsWith(_T("METER")))
12527 depth_unit_type = DEPTH_UNIT_METERS;
12528
12529 } else {
12530 if (m_singleChart) {
12531 depth_unit_type = m_singleChart->GetDepthUnitType();
12532 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12533 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12534 }
12535 }
12536
12537 emboss_data *ped = NULL;
12538 switch (depth_unit_type) {
12539 case DEPTH_UNIT_FEET:
12540 ped = m_pEM_Feet;
12541 break;
12542 case DEPTH_UNIT_METERS:
12543 ped = m_pEM_Meters;
12544 break;
12545 case DEPTH_UNIT_FATHOMS:
12546 ped = m_pEM_Fathoms;
12547 break;
12548 default:
12549 return NULL;
12550 }
12551
12552 ped->x = (GetVP().pix_width - ped->width);
12553
12554 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12555 wxRect r = m_Compass->GetRect();
12556 ped->y = r.y + r.height;
12557 } else {
12558 ped->y = 40;
12559 }
12560 return ped;
12561}
12562
12563void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12564 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12565 wxFont font;
12566 if (style->embossFont == wxEmptyString) {
12567 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12568 font = *dFont;
12569 font.SetPointSize(60);
12570 font.SetWeight(wxFONTWEIGHT_BOLD);
12571 } else
12572 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12573 wxFONTWEIGHT_BOLD, false, style->embossFont);
12574
12575 int emboss_width = 500;
12576 int emboss_height = 200;
12577
12578 // Free any existing emboss maps
12579 delete m_pEM_Feet;
12580 delete m_pEM_Meters;
12581 delete m_pEM_Fathoms;
12582
12583 // Create the 3 DepthUnit emboss map structures
12584 m_pEM_Feet =
12585 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12586 m_pEM_Meters =
12587 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12588 m_pEM_Fathoms =
12589 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12590}
12591
12592#define OVERZOOM_TEXT _("OverZoom")
12593
12594void ChartCanvas::SetOverzoomFont() {
12595 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12596 int w, h;
12597
12598 wxFont font;
12599 if (style->embossFont == wxEmptyString) {
12600 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12601 font = *dFont;
12602 font.SetPointSize(40);
12603 font.SetWeight(wxFONTWEIGHT_BOLD);
12604 } else
12605 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12606 wxFONTWEIGHT_BOLD, false, style->embossFont);
12607
12608 wxClientDC dc(this);
12609 dc.SetFont(font);
12610 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12611
12612 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12613 font.SetPointSize(font.GetPointSize() - 1);
12614 dc.SetFont(font);
12615 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12616 }
12617 m_overzoomFont = font;
12618 m_overzoomTextWidth = w;
12619 m_overzoomTextHeight = h;
12620}
12621
12622void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12623 delete m_pEM_OverZoom;
12624
12625 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12626 m_pEM_OverZoom =
12627 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12628 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12629}
12630
12631emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12632 int height, const wxString &str,
12633 ColorScheme cs) {
12634 int *pmap;
12635
12636 // Create a temporary bitmap
12637 wxBitmap bmp(width, height, -1);
12638
12639 // Create a memory DC
12640 wxMemoryDC temp_dc;
12641 temp_dc.SelectObject(bmp);
12642
12643 // Paint on it
12644 temp_dc.SetBackground(*wxWHITE_BRUSH);
12645 temp_dc.SetTextBackground(*wxWHITE);
12646 temp_dc.SetTextForeground(*wxBLACK);
12647
12648 temp_dc.Clear();
12649
12650 temp_dc.SetFont(font);
12651
12652 int str_w, str_h;
12653 temp_dc.GetTextExtent(str, &str_w, &str_h);
12654 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12655 temp_dc.DrawText(str, 1, 1);
12656
12657 // Deselect the bitmap
12658 temp_dc.SelectObject(wxNullBitmap);
12659
12660 // Convert bitmap the wxImage for manipulation
12661 wxImage img = bmp.ConvertToImage();
12662
12663 int image_width = str_w * 105 / 100;
12664 int image_height = str_h * 105 / 100;
12665 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12666 wxMin(image_height, img.GetHeight()));
12667 wxImage imgs = img.GetSubImage(r);
12668
12669 double val_factor;
12670 switch (cs) {
12671 case GLOBAL_COLOR_SCHEME_DAY:
12672 default:
12673 val_factor = 1;
12674 break;
12675 case GLOBAL_COLOR_SCHEME_DUSK:
12676 val_factor = .5;
12677 break;
12678 case GLOBAL_COLOR_SCHEME_NIGHT:
12679 val_factor = .25;
12680 break;
12681 }
12682
12683 int val;
12684 int index;
12685 const int w = imgs.GetWidth();
12686 const int h = imgs.GetHeight();
12687 pmap = (int *)calloc(w * h * sizeof(int), 1);
12688 // Create emboss map by differentiating the emboss image
12689 // and storing integer results in pmap
12690 // n.b. since the image is B/W, it is sufficient to check
12691 // one channel (i.e. red) only
12692 for (int y = 1; y < h - 1; y++) {
12693 for (int x = 1; x < w - 1; x++) {
12694 val =
12695 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
12696 val = (int)(val * val_factor);
12697 index = (y * w) + x;
12698 pmap[index] = val;
12699 }
12700 }
12701
12702 emboss_data *pret = new emboss_data;
12703 pret->pmap = pmap;
12704 pret->width = w;
12705 pret->height = h;
12706
12707 return pret;
12708}
12709
12710void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12711 Track *active_track = NULL;
12712 for (Track *pTrackDraw : g_TrackList) {
12713 if (g_pActiveTrack == pTrackDraw) {
12714 active_track = pTrackDraw;
12715 continue;
12716 }
12717
12718 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
12719 }
12720
12721 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12722}
12723
12724void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12725 Track *active_track = NULL;
12726 for (Track *pTrackDraw : g_TrackList) {
12727 if (g_pActiveTrack == pTrackDraw) {
12728 active_track = pTrackDraw;
12729 break;
12730 }
12731 }
12732 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12733}
12734
12735void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12736 Route *active_route = NULL;
12737
12738 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12739 node = node->GetNext()) {
12740 Route *pRouteDraw = node->GetData();
12741 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12742 active_route = pRouteDraw;
12743 continue;
12744 }
12745
12746 // if(m_canvasIndex == 1)
12747 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
12748 }
12749
12750 // Draw any active or selected route (or track) last, so that is is always on
12751 // top
12752 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12753}
12754
12755void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12756 Route *active_route = NULL;
12757
12758 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12759 node = node->GetNext()) {
12760 Route *pRouteDraw = node->GetData();
12761 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12762 active_route = pRouteDraw;
12763 break;
12764 }
12765 }
12766 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12767}
12768
12769void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12770 if (!pWayPointMan) return;
12771
12772 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12773
12774 while (node) {
12775 RoutePoint *pWP = node->GetData();
12776 if (pWP) {
12777 if (pWP->m_bIsInRoute) {
12778 node = node->GetNext();
12779 continue;
12780 }
12781
12782 /* technically incorrect... waypoint has bounding box */
12783 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
12784 RoutePointGui(*pWP).Draw(dc, this, NULL);
12785 else {
12786 // Are Range Rings enabled?
12787 if (pWP->GetShowWaypointRangeRings() &&
12788 (pWP->GetWaypointRangeRingsNumber() > 0)) {
12789 double factor = 1.00;
12790 if (pWP->GetWaypointRangeRingsStepUnits() ==
12791 1) // convert kilometers to NMi
12792 factor = 1 / 1.852;
12793
12794 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
12795 pWP->GetWaypointRangeRingsStep() / 60.;
12796 radius *= 2; // Fudge factor
12797
12798 LLBBox radar_box;
12799 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
12800 pWP->m_lat + radius, pWP->m_lon + radius);
12801 if (!BltBBox.IntersectOut(radar_box)) {
12802 RoutePointGui(*pWP).Draw(dc, this, NULL);
12803 }
12804 }
12805 }
12806 }
12807
12808 node = node->GetNext();
12809 }
12810}
12811
12812void ChartCanvas::DrawBlinkObjects(void) {
12813 // All RoutePoints
12814 wxRect update_rect;
12815
12816 if (!pWayPointMan) return;
12817
12818 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12819
12820 while (node) {
12821 RoutePoint *pWP = node->GetData();
12822 if (pWP) {
12823 if (pWP->m_bBlink) {
12824 update_rect.Union(pWP->CurrentRect_in_DC);
12825 }
12826 }
12827
12828 node = node->GetNext();
12829 }
12830 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
12831}
12832
12833void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
12834 // draw anchor watch rings, if activated
12835
12836 if (pAnchorWatchPoint1 || pAnchorWatchPoint2) {
12837 wxPoint r1, r2;
12838 wxPoint lAnchorPoint1, lAnchorPoint2;
12839 double lpp1 = 0.0;
12840 double lpp2 = 0.0;
12841 if (pAnchorWatchPoint1) {
12842 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
12843 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
12844 &lAnchorPoint1);
12845 }
12846 if (pAnchorWatchPoint2) {
12847 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
12848 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
12849 &lAnchorPoint2);
12850 }
12851
12852 wxPen ppPeng(GetGlobalColor(_T ( "UGREN" )), 2);
12853 wxPen ppPenr(GetGlobalColor(_T ( "URED" )), 2);
12854
12855 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
12856 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
12857 dc.SetBrush(*ppBrush);
12858
12859 if (lpp1 > 0) {
12860 dc.SetPen(ppPeng);
12861 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12862 }
12863
12864 if (lpp2 > 0) {
12865 dc.SetPen(ppPeng);
12866 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12867 }
12868
12869 if (lpp1 < 0) {
12870 dc.SetPen(ppPenr);
12871 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12872 }
12873
12874 if (lpp2 < 0) {
12875 dc.SetPen(ppPenr);
12876 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12877 }
12878 }
12879}
12880
12881double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
12882 double lpp = 0.;
12883 wxPoint r1;
12884 wxPoint lAnchorPoint;
12885 double d1 = 0.0;
12886 double dabs;
12887 double tlat1, tlon1;
12888
12889 if (pAnchorWatchPoint) {
12890 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
12891 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
12892 dabs = fabs(d1 / 1852.);
12893 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
12894 &tlat1, &tlon1);
12895 GetCanvasPointPix(tlat1, tlon1, &r1);
12896 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
12897 &lAnchorPoint);
12898 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
12899 pow((double)(lAnchorPoint.y - r1.y), 2));
12900
12901 // This is an entry watch
12902 if (d1 < 0) lpp = -lpp;
12903 }
12904 return lpp;
12905}
12906
12907//------------------------------------------------------------------------------------------
12908// Tides Support
12909//------------------------------------------------------------------------------------------
12910void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
12911 if (!ptcmgr) return;
12912
12913 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
12914
12915 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12916 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12917 double lon = pIDX->IDX_lon;
12918 double lat = pIDX->IDX_lat;
12919
12920 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12921 if ((type == 't') || (type == 'T')) {
12922 if (BBox.Contains(lat, lon)) {
12923 // Manage the point selection list
12924 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
12925 }
12926 }
12927 }
12928}
12929
12930extern wxDateTime gTimeSource;
12931
12932void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
12933 if (!ptcmgr) return;
12934
12935 wxDateTime this_now = gTimeSource;
12936 bool cur_time = !gTimeSource.IsValid();
12937 if (cur_time) this_now = wxDateTime::Now();
12938 time_t t_this_now = this_now.GetTicks();
12939
12940 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
12941 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
12942 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
12943 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )), 1,
12944 wxPENSTYLE_SOLID);
12945 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
12946 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )), 1,
12947 wxPENSTYLE_SOLID);
12948
12949 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
12950 GetGlobalColor(_T ( "GREEN1" )), wxBRUSHSTYLE_SOLID);
12951 // wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush (
12952 // GetGlobalColor ( _T ( "UINFD" ) ), wxSOLID );
12953 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
12954 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )),
12955 wxBRUSHSTYLE_SOLID);
12956 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
12957 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )),
12958 wxBRUSHSTYLE_SOLID);
12959
12960 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
12961 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
12962 int font_size = wxMax(10, dFont->GetPointSize());
12963 font_size /= g_Platform->GetDisplayDIPMult(this);
12964 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
12965 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
12966 false, dFont->GetFaceName());
12967
12968 dc.SetPen(*pblack_pen);
12969 dc.SetBrush(*pgreen_brush);
12970
12971 wxBitmap bm;
12972 switch (m_cs) {
12973 case GLOBAL_COLOR_SCHEME_DAY:
12974 bm = m_bmTideDay;
12975 break;
12976 case GLOBAL_COLOR_SCHEME_DUSK:
12977 bm = m_bmTideDusk;
12978 break;
12979 case GLOBAL_COLOR_SCHEME_NIGHT:
12980 bm = m_bmTideNight;
12981 break;
12982 default:
12983 bm = m_bmTideDay;
12984 break;
12985 }
12986
12987 int bmw = bm.GetWidth();
12988 int bmh = bm.GetHeight();
12989
12990 float scale_factor = 1.0;
12991
12992 // Set the onscreen size of the symbol
12993 // Compensate for various display resolutions
12994 float icon_pixelRefDim = 45;
12995
12996#if 0
12997 float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *25 / 1000; // Intended physical rendered size onscreen
12998 nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 8);
12999 nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 15);
13000 float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
13001#endif
13002
13003#ifndef __ANDROID__
13004 // another method is simply to declare that the icon shall be x times the size
13005 // of a raster symbol (e.g.BOYLAT)
13006 // This is a bit of a hack that will suffice until until we get fully
13007 // scalable ENC symbol sets
13008 // float nominal_icon_size_pixels = 48; // 3 x 16
13009 // float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
13010
13011 // or, x times size of text font
13012 wxScreenDC sdc;
13013 int height;
13014 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13015 height *= g_Platform->GetDisplayDIPMult(this);
13016 float nominal_icon_size_pixels = 48; // 3 x 16
13017 float pix_factor = (2 * height) / nominal_icon_size_pixels;
13018
13019#else
13020 // Yet another method goes like this:
13021 // Set the onscreen size of the symbol
13022 // Compensate for various display resolutions
13023 // Develop empirically, making a symbol about 16 mm tall
13024 double symHeight =
13025 icon_pixelRefDim /
13026 GetPixPerMM(); // from draw instructions, symbol is xx pix high
13027 double targetHeight0 = 16.0;
13028
13029 // But we want to scale the size down for smaller displays
13030 double displaySize = m_display_size_mm;
13031 displaySize = wxMax(displaySize, 100);
13032
13033 float targetHeight = wxMin(targetHeight0, displaySize / 15);
13034
13035 double pix_factor = targetHeight / symHeight;
13036#endif
13037
13038 scale_factor *= pix_factor;
13039
13040 float user_scale_factor = g_ChartScaleFactorExp;
13041 if (g_ChartScaleFactorExp > 1.0)
13042 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13043 1.2; // soften the scale factor a bit
13044
13045 scale_factor *= user_scale_factor;
13046 scale_factor *= GetContentScaleFactor();
13047
13048 {
13049 double marge = 0.05;
13050 std::vector<LLBBox> drawn_boxes;
13051 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13052 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13053
13054 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13055 if ((type == 't') || (type == 'T')) // only Tides
13056 {
13057 double lon = pIDX->IDX_lon;
13058 double lat = pIDX->IDX_lat;
13059
13060 if (BBox.ContainsMarge(lat, lon, marge)) {
13061 // Avoid drawing detailed graphic for duplicate tide stations
13062 if (GetVP().chart_scale < 500000) {
13063 bool bdrawn = false;
13064 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13065 if (drawn_boxes[i].Contains(lat, lon)) {
13066 bdrawn = true;
13067 break;
13068 }
13069 }
13070 if (bdrawn) continue; // the station loop
13071
13072 LLBBox this_box;
13073 this_box.Set(lat, lon, lat, lon);
13074 this_box.EnLarge(.005);
13075 drawn_boxes.push_back(this_box);
13076 }
13077
13078 wxPoint r;
13079 GetCanvasPointPix(lat, lon, &r);
13080 // draw standard icons
13081 if (GetVP().chart_scale > 500000) {
13082 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13083 }
13084 // draw "extended" icons
13085 else {
13086 dc.SetFont(*plabelFont);
13087 {
13088 {
13089 float val, nowlev;
13090 float ltleve = 0.;
13091 float htleve = 0.;
13092 time_t tctime;
13093 time_t lttime = 0;
13094 time_t httime = 0;
13095 bool wt;
13096 // define if flood or ebb in the last ten minutes and verify if
13097 // data are useable
13098 if (ptcmgr->GetTideFlowSens(
13099 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13100 pIDX->IDX_rec_num, nowlev, val, wt)) {
13101 // search forward the first HW or LW near "now" ( starting at
13102 // "now" - ten minutes )
13103 ptcmgr->GetHightOrLowTide(
13104 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13105 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13106 wt, pIDX->IDX_rec_num, val, tctime);
13107 if (wt) {
13108 httime = tctime;
13109 htleve = val;
13110 } else {
13111 lttime = tctime;
13112 ltleve = val;
13113 }
13114 wt = !wt;
13115
13116 // then search opposite tide near "now"
13117 if (tctime > t_this_now) // search backward
13118 ptcmgr->GetHightOrLowTide(
13119 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13120 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13121 pIDX->IDX_rec_num, val, tctime);
13122 else
13123 // or search forward
13124 ptcmgr->GetHightOrLowTide(
13125 t_this_now, FORWARD_TEN_MINUTES_STEP,
13126 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13127 val, tctime);
13128 if (wt) {
13129 httime = tctime;
13130 htleve = val;
13131 } else {
13132 lttime = tctime;
13133 ltleve = val;
13134 }
13135
13136 // draw the tide rectangle:
13137
13138 // tide icon rectangle has default pre-scaled width = 12 ,
13139 // height = 45
13140 int width = (int)(12 * scale_factor + 0.5);
13141 int height = (int)(45 * scale_factor + 0.5);
13142 int linew = wxMax(1, (int)(scale_factor));
13143 int xDraw = r.x - (width / 2);
13144 int yDraw = r.y - (height / 2);
13145
13146 // process tide state ( %height and flow sens )
13147 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13148 int hs = (httime > lttime) ? -4 : 4;
13149 hs *= (int)(scale_factor + 0.5);
13150 if (ts > 0.995 || ts < 0.005) hs = 0;
13151 int ht_y = (int)(height * ts);
13152
13153 // draw yellow tide rectangle outlined in black
13154 pblack_pen->SetWidth(linew);
13155 dc.SetPen(*pblack_pen);
13156 dc.SetBrush(*pyelo_brush);
13157 dc.DrawRectangle(xDraw, yDraw, width, height);
13158
13159 // draw blue rectangle as water height, smaller in width than
13160 // yellow rectangle
13161 dc.SetPen(*pblue_pen);
13162 dc.SetBrush(*pblue_brush);
13163 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13164 (width - (4 * linew)), height - ht_y);
13165
13166 // draw sens arrows (ensure they are not "under-drawn" by top
13167 // line of blue rectangle )
13168 int hl;
13169 wxPoint arrow[3];
13170 arrow[0].x = xDraw + 2 * linew;
13171 arrow[1].x = xDraw + width / 2;
13172 arrow[2].x = xDraw + width - 2 * linew;
13173 pyelo_pen->SetWidth(linew);
13174 pblue_pen->SetWidth(linew);
13175 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13176 {
13177 hl = (int)(height * 0.25) + yDraw;
13178 arrow[0].y = hl;
13179 arrow[1].y = hl + hs;
13180 arrow[2].y = hl;
13181 if (ts < 0.15)
13182 dc.SetPen(*pyelo_pen);
13183 else
13184 dc.SetPen(*pblue_pen);
13185 dc.DrawLines(3, arrow);
13186 }
13187 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13188 {
13189 hl = (int)(height * 0.5) + yDraw;
13190 arrow[0].y = hl;
13191 arrow[1].y = hl + hs;
13192 arrow[2].y = hl;
13193 if (ts < 0.40)
13194 dc.SetPen(*pyelo_pen);
13195 else
13196 dc.SetPen(*pblue_pen);
13197 dc.DrawLines(3, arrow);
13198 }
13199 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13200 {
13201 hl = (int)(height * 0.75) + yDraw;
13202 arrow[0].y = hl;
13203 arrow[1].y = hl + hs;
13204 arrow[2].y = hl;
13205 if (ts < 0.65)
13206 dc.SetPen(*pyelo_pen);
13207 else
13208 dc.SetPen(*pblue_pen);
13209 dc.DrawLines(3, arrow);
13210 }
13211 // draw tide level text
13212 wxString s;
13213 s.Printf(_T("%3.1f"), nowlev);
13214 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13215 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13216 int wx1;
13217 dc.GetTextExtent(s, &wx1, NULL);
13218 wx1 *= g_Platform->GetDisplayDIPMult(this);
13219 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13220 }
13221 }
13222 }
13223 }
13224 }
13225 }
13226 }
13227 }
13228}
13229
13230//------------------------------------------------------------------------------------------
13231// Currents Support
13232//------------------------------------------------------------------------------------------
13233
13234void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13235 if (!ptcmgr) return;
13236
13237 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13238
13239 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13240 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13241 double lon = pIDX->IDX_lon;
13242 double lat = pIDX->IDX_lat;
13243
13244 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13245 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13246 if ((BBox.Contains(lat, lon))) {
13247 // Manage the point selection list
13248 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13249 }
13250 }
13251 }
13252}
13253
13254void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13255 if (!ptcmgr) return;
13256
13257 float tcvalue, dir;
13258 bool bnew_val;
13259 char sbuf[20];
13260 wxFont *pTCFont;
13261 double lon_last = 0.;
13262 double lat_last = 0.;
13263 // arrow size for Raz Blanchard : 12 knots north
13264 double marge = 0.2;
13265 bool cur_time = !gTimeSource.IsValid();
13266
13267 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13268 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13269
13270 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
13271 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
13272 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13273 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )), 1,
13274 wxPENSTYLE_SOLID);
13275 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13276 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )),
13277 wxBRUSHSTYLE_SOLID);
13278 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13279 GetGlobalColor(_T ( "UIBDR" )), wxBRUSHSTYLE_SOLID);
13280 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13281 GetGlobalColor(_T ( "UINFD" )), wxBRUSHSTYLE_SOLID);
13282
13283 double skew_angle = GetVPRotation();
13284
13285 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13286 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13287 int font_size = wxMax(10, dFont->GetPointSize());
13288 font_size /= g_Platform->GetDisplayDIPMult(this);
13289 pTCFont = FontMgr::Get().FindOrCreateFont(
13290 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13291 false, dFont->GetFaceName());
13292
13293 float scale_factor = 1.0;
13294
13295 // Set the onscreen size of the symbol
13296 // Compensate for various display resolutions
13297
13298#if 0
13299 float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *3 / 1000; // Intended physical rendered size onscreen
13300 nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 2);
13301 nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 4);
13302 float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
13303#endif
13304
13305#if 0
13306 // another method is simply to declare that the icon shall be x times the size of a raster symbol (e.g.BOYLAT)
13307 // This is a bit of a hack that will suffice until until we get fully scalable ENC symbol sets
13308 float nominal_icon_size_pixels = 6; // 16 / 3
13309 float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
13310#endif
13311
13312#ifndef __ANDROID__
13313 // or, x times size of text font
13314 wxScreenDC sdc;
13315 int height;
13316 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13317 height *= g_Platform->GetDisplayDIPMult(this);
13318 float nominal_icon_size_pixels = 15;
13319 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13320
13321#else
13322 // Yet another method goes like this:
13323 // Set the onscreen size of the symbol
13324 // Compensate for various display resolutions
13325 // Develop empirically....
13326 float icon_pixelRefDim = 5;
13327
13328 double symHeight =
13329 icon_pixelRefDim /
13330 GetPixPerMM(); // from draw instructions, symbol is xx pix high
13331 double targetHeight0 = 2.0;
13332
13333 // But we want to scale the size down for smaller displays
13334 double displaySize = m_display_size_mm;
13335 displaySize = wxMax(displaySize, 100);
13336
13337 float targetHeight = wxMin(targetHeight0, displaySize / 50);
13338 double pix_factor = targetHeight / symHeight;
13339#endif
13340
13341 scale_factor *= pix_factor;
13342
13343 float user_scale_factor = g_ChartScaleFactorExp;
13344 if (g_ChartScaleFactorExp > 1.0)
13345 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13346 1.2; // soften the scale factor a bit
13347
13348 scale_factor *= user_scale_factor;
13349
13350 scale_factor *= GetContentScaleFactor();
13351
13352 {
13353 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13354 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13355 double lon = pIDX->IDX_lon;
13356 double lat = pIDX->IDX_lat;
13357
13358 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13359 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13360 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13361 wxPoint r;
13362 GetCanvasPointPix(lat, lon, &r);
13363
13364 wxPoint d[4]; // points of a diamond at the current station location
13365 int dd = (int)(5.0 * scale_factor + 0.5);
13366 d[0].x = r.x;
13367 d[0].y = r.y + dd;
13368 d[1].x = r.x + dd;
13369 d[1].y = r.y;
13370 d[2].x = r.x;
13371 d[2].y = r.y - dd;
13372 d[3].x = r.x - dd;
13373 d[3].y = r.y;
13374
13375 if (1) {
13376 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13377 dc.SetPen(*pblack_pen);
13378 dc.SetBrush(*porange_brush);
13379 dc.DrawPolygon(4, d);
13380
13381 if (type == 'C') {
13382 dc.SetBrush(*pblack_brush);
13383 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13384 }
13385
13386 if (GetVP().chart_scale < 1000000) {
13387 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13388 continue;
13389 } else
13390 continue;
13391
13392 if (1 /*type == 'c'*/) {
13393 {
13394 // Get the display pixel location of the current station
13395 int pixxc, pixyc;
13396 pixxc = r.x;
13397 pixyc = r.y;
13398
13399 // Adjust drawing size using logarithmic scale. tcvalue is
13400 // current in knots
13401 double a1 = fabs(tcvalue) * 10.;
13402 // Current values <= 0.1 knot will have no arrow
13403 a1 = wxMax(1.0, a1);
13404 double a2 = log10(a1);
13405
13406 float cscale = scale_factor * a2 * 0.4;
13407
13408 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13409 dc.SetPen(*porange_pen);
13410 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13411 cscale);
13412 // Draw text, if enabled
13413
13414 if (bDrawCurrentValues) {
13415 dc.SetFont(*pTCFont);
13416 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13417 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13418 }
13419 }
13420 } // scale
13421 }
13422 /* This is useful for debugging the TC database
13423 else
13424 {
13425 dc.SetPen ( *porange_pen );
13426 dc.SetBrush ( *pgray_brush );
13427 dc.DrawPolygon ( 4, d );
13428 }
13429 */
13430 }
13431 lon_last = lon;
13432 lat_last = lat;
13433 }
13434 }
13435 }
13436}
13437
13438void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13439 pCwin = new TCWin(this, x, y, pvIDX);
13440}
13441
13442#define NUM_CURRENT_ARROW_POINTS 9
13443static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13444 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13445 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13446 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13447
13448void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13449 double scale) {
13450 if (scale > 1e-2) {
13451 float sin_rot = sin(rot_angle * PI / 180.);
13452 float cos_rot = cos(rot_angle * PI / 180.);
13453
13454 // Move to the first point
13455
13456 float xt = CurrentArrowArray[0].x;
13457 float yt = CurrentArrowArray[0].y;
13458
13459 float xp = (xt * cos_rot) - (yt * sin_rot);
13460 float yp = (xt * sin_rot) + (yt * cos_rot);
13461 int x1 = (int)(xp * scale);
13462 int y1 = (int)(yp * scale);
13463
13464 // Walk thru the point list
13465 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13466 xt = CurrentArrowArray[ip].x;
13467 yt = CurrentArrowArray[ip].y;
13468
13469 float xp = (xt * cos_rot) - (yt * sin_rot);
13470 float yp = (xt * sin_rot) + (yt * cos_rot);
13471 int x2 = (int)(xp * scale);
13472 int y2 = (int)(yp * scale);
13473
13474 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13475
13476 x1 = x2;
13477 y1 = y2;
13478 }
13479 }
13480}
13481
13482wxString ChartCanvas::FindValidUploadPort() {
13483 wxString port;
13484 // Try to use the saved persistent upload port first
13485 if (!g_uploadConnection.IsEmpty() &&
13486 g_uploadConnection.StartsWith(_T("Serial"))) {
13487 port = g_uploadConnection;
13488 }
13489
13490 else {
13491 // If there is no persistent upload port recorded (yet)
13492 // then use the first available serial connection which has output defined.
13493 for (auto *cp : TheConnectionParams()) {
13494 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13495 port << _T("Serial:") << cp->Port;
13496 }
13497 }
13498 return port;
13499}
13500
13501void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13502 if (!win) return;
13503
13504 if (NULL == g_pais_query_dialog_active) {
13505 int pos_x = g_ais_query_dialog_x;
13506 int pos_y = g_ais_query_dialog_y;
13507
13508 if (g_pais_query_dialog_active) {
13509 g_pais_query_dialog_active->Destroy();
13510 g_pais_query_dialog_active = new AISTargetQueryDialog();
13511 } else {
13512 g_pais_query_dialog_active = new AISTargetQueryDialog();
13513 }
13514
13515 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13516 wxPoint(pos_x, pos_y));
13517
13518 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13519 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13520 g_pais_query_dialog_active->SetMMSI(mmsi);
13521 g_pais_query_dialog_active->UpdateText();
13522 wxSize sz = g_pais_query_dialog_active->GetSize();
13523
13524 bool b_reset_pos = false;
13525#ifdef __WXMSW__
13526 // Support MultiMonitor setups which an allow negative window positions.
13527 // If the requested window title bar does not intersect any installed
13528 // monitor, then default to simple primary monitor positioning.
13529 RECT frame_title_rect;
13530 frame_title_rect.left = pos_x;
13531 frame_title_rect.top = pos_y;
13532 frame_title_rect.right = pos_x + sz.x;
13533 frame_title_rect.bottom = pos_y + 30;
13534
13535 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13536 b_reset_pos = true;
13537#else
13538
13539 // Make sure drag bar (title bar) of window intersects wxClient Area of
13540 // screen, with a little slop...
13541 wxRect window_title_rect; // conservative estimate
13542 window_title_rect.x = pos_x;
13543 window_title_rect.y = pos_y;
13544 window_title_rect.width = sz.x;
13545 window_title_rect.height = 30;
13546
13547 wxRect ClientRect = wxGetClientDisplayRect();
13548 ClientRect.Deflate(
13549 60, 60); // Prevent the new window from being too close to the edge
13550 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13551
13552#endif
13553
13554 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13555
13556 } else {
13557 g_pais_query_dialog_active->SetMMSI(mmsi);
13558 g_pais_query_dialog_active->UpdateText();
13559 }
13560
13561 g_pais_query_dialog_active->Show();
13562}
13563
13564void ChartCanvas::ToggleCanvasQuiltMode(void) {
13565 bool cur_mode = GetQuiltMode();
13566
13567 if (!GetQuiltMode())
13568 SetQuiltMode(true);
13569 else if (GetQuiltMode()) {
13570 SetQuiltMode(false);
13571 g_sticky_chart = GetQuiltReferenceChartIndex();
13572 }
13573
13574 if (cur_mode != GetQuiltMode()) {
13575 SetupCanvasQuiltMode();
13576 DoCanvasUpdate();
13577 InvalidateGL();
13578 Refresh();
13579 }
13580 // TODO What to do about this?
13581 // g_bQuiltEnable = GetQuiltMode();
13582
13583 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13584 if (ps52plib) ps52plib->GenerateStateHash();
13585
13586 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13587 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13588}
13589
13590void ChartCanvas::DoCanvasStackDelta(int direction) {
13591 if (!GetQuiltMode()) {
13592 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13593 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13594 if ((current_stack_index + direction) < 0) return;
13595
13596 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13597 int new_dbIndex =
13598 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13599
13600 if (IsChartQuiltableRef(new_dbIndex)) {
13601 ToggleCanvasQuiltMode();
13602 SelectQuiltRefdbChart(new_dbIndex);
13603 m_bpersistent_quilt = false;
13604 }
13605 } else {
13606 SelectChartFromStack(current_stack_index + direction);
13607 }
13608 } else {
13609 std::vector<int> piano_chart_index_array =
13610 GetQuiltExtendedStackdbIndexArray();
13611 int refdb = GetQuiltRefChartdbIndex();
13612
13613 // Find the ref chart in the stack
13614 int current_index = -1;
13615 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13616 if (refdb == piano_chart_index_array[i]) {
13617 current_index = i;
13618 break;
13619 }
13620 }
13621 if (current_index == -1) return;
13622
13623 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13624 int target_family = ctet.GetChartFamily();
13625
13626 int new_index = -1;
13627 int check_index = current_index + direction;
13628 bool found = false;
13629 int check_dbIndex = -1;
13630 int new_dbIndex = -1;
13631
13632 // When quilted. switch within the same chart family
13633 while (!found &&
13634 (unsigned int)check_index < piano_chart_index_array.size() &&
13635 (check_index >= 0)) {
13636 check_dbIndex = piano_chart_index_array[check_index];
13637 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13638 if (target_family == cte.GetChartFamily()) {
13639 found = true;
13640 new_index = check_index;
13641 new_dbIndex = check_dbIndex;
13642 break;
13643 }
13644
13645 check_index += direction;
13646 }
13647
13648 if (!found) return;
13649
13650 if (!IsChartQuiltableRef(new_dbIndex)) {
13651 ToggleCanvasQuiltMode();
13652 SelectdbChart(new_dbIndex);
13653 m_bpersistent_quilt = true;
13654 } else {
13655 SelectQuiltRefChart(new_index);
13656 }
13657 }
13658
13659 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13660 // (checkmarks etc)
13661 SetQuiltChartHiLiteIndex(-1);
13662
13663 ReloadVP();
13664}
13665
13666//--------------------------------------------------------------------------------------------------------
13667//
13668// Toolbar support
13669//
13670//--------------------------------------------------------------------------------------------------------
13671
13672void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13673 // Handle the per-canvas toolbar clicks here
13674
13675 switch (event.GetId()) {
13676 case ID_ZOOMIN: {
13677 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13678 break;
13679 }
13680
13681 case ID_ZOOMOUT: {
13682 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13683 break;
13684 }
13685
13686 case ID_STKUP:
13687 DoCanvasStackDelta(1);
13688 DoCanvasUpdate();
13689 break;
13690
13691 case ID_STKDN:
13692 DoCanvasStackDelta(-1);
13693 DoCanvasUpdate();
13694 break;
13695
13696 case ID_FOLLOW: {
13697 TogglebFollow();
13698 break;
13699 }
13700
13701 case ID_CURRENT: {
13702 ShowCurrents(!GetbShowCurrent());
13703 ReloadVP();
13704 Refresh(false);
13705 break;
13706 }
13707
13708 case ID_TIDE: {
13709 ShowTides(!GetbShowTide());
13710 ReloadVP();
13711 Refresh(false);
13712 break;
13713 }
13714
13715 case ID_ROUTE: {
13716 if (0 == m_routeState) {
13717 StartRoute();
13718 } else {
13719 FinishRoute();
13720 }
13721
13722#ifdef __ANDROID__
13723 androidSetRouteAnnunciator(m_routeState == 1);
13724#endif
13725 break;
13726 }
13727
13728 case ID_AIS: {
13729 SetAISCanvasDisplayStyle(-1);
13730 break;
13731 }
13732
13733 default:
13734 break;
13735 }
13736
13737 // And then let gFrame handle the rest....
13738 event.Skip();
13739}
13740
13741void ChartCanvas::SetShowAIS(bool show) {
13742 m_bShowAIS = show;
13743 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13744 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13745}
13746
13747void ChartCanvas::SetAttenAIS(bool show) {
13748 m_bShowAISScaled = show;
13749 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13750 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13751}
13752
13753void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
13754 // make some arrays to hold the dfferences between cycle steps
13755 // show all, scaled, hide all
13756 bool bShowAIS_Array[3] = {true, true, false};
13757 bool bShowScaled_Array[3] = {false, true, true};
13758 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
13759 _("Attenuate less critical AIS targets"),
13760 _("Hide AIS Targets")};
13761 wxString iconName_Array[3] = {_T("AIS"), _T("AIS_Suppressed"),
13762 _T("AIS_Disabled")};
13763 int ArraySize = 3;
13764 int AIS_Toolbar_Switch = 0;
13765 if (StyleIndx == -1) { // -1 means coming from toolbar button
13766 // find current state of switch
13767 for (int i = 1; i < ArraySize; i++) {
13768 if ((bShowAIS_Array[i] == m_bShowAIS) &&
13769 (bShowScaled_Array[i] == m_bShowAISScaled))
13770 AIS_Toolbar_Switch = i;
13771 }
13772 AIS_Toolbar_Switch++; // we did click so continu with next item
13773 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
13774 AIS_Toolbar_Switch++;
13775
13776 } else { // coming from menu bar.
13777 AIS_Toolbar_Switch = StyleIndx;
13778 }
13779 // make sure we are not above array
13780 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
13781
13782 int AIS_Toolbar_Switch_Next =
13783 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
13784 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
13785 AIS_Toolbar_Switch_Next++;
13786 if (AIS_Toolbar_Switch_Next >= ArraySize)
13787 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
13788
13789 // Set found values to global and member variables
13790 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
13791 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
13792}
13793
13794void ChartCanvas::TouchAISToolActive(void) {}
13795
13796void ChartCanvas::UpdateAISTBTool(void) {}
13797
13798//---------------------------------------------------------------------------------
13799//
13800// Compass/GPS status icon support
13801//
13802//---------------------------------------------------------------------------------
13803
13804void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
13805 // Look for change in overlap or positions
13806 bool b_update = false;
13807 int cc1_edge_comp = 2;
13808 wxRect rect = m_Compass->GetRect();
13809 wxSize parent_size = GetSize();
13810
13811 parent_size *= m_displayScale;
13812
13813 // check to see if it would overlap if it was in its home position (upper
13814 // right)
13815 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
13816 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
13817 wxRect compass_rect(compass_pt, rect.GetSize());
13818
13819 m_Compass->Move(compass_pt);
13820
13821 if (m_Compass && m_Compass->IsShown())
13822 m_Compass->UpdateStatus(b_force_new | b_update);
13823
13824 wxPoint note_point =
13825 wxPoint(compass_rect.x - compass_rect.width, compass_rect.y);
13826 m_notification_button->Move(note_point);
13827
13828 m_notification_button->UpdateStatus();
13829 if (b_force_new | b_update) Refresh();
13830}
13831
13832void ChartCanvas::SelectChartFromStack(int index, bool bDir,
13833 ChartTypeEnum New_Type,
13834 ChartFamilyEnum New_Family) {
13835 if (!GetpCurrentStack()) return;
13836 if (!ChartData) return;
13837
13838 if (index < GetpCurrentStack()->nEntry) {
13839 // Open the new chart
13840 ChartBase *pTentative_Chart;
13841 pTentative_Chart = ChartData->OpenStackChartConditional(
13842 GetpCurrentStack(), index, bDir, New_Type, New_Family);
13843
13844 if (pTentative_Chart) {
13845 if (m_singleChart) m_singleChart->Deactivate();
13846
13847 m_singleChart = pTentative_Chart;
13848 m_singleChart->Activate();
13849
13850 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13851 GetpCurrentStack(), m_singleChart->GetFullPath());
13852 }
13853
13854 // Setup the view
13855 double zLat, zLon;
13856 if (m_bFollow) {
13857 zLat = gLat;
13858 zLon = gLon;
13859 } else {
13860 zLat = m_vLat;
13861 zLon = m_vLon;
13862 }
13863
13864 double best_scale_ppm = GetBestVPScale(m_singleChart);
13865 double rotation = GetVPRotation();
13866 double oldskew = GetVPSkew();
13867 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
13868
13869 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
13870 if (fabs(oldskew) > 0.0001) rotation = 0.0;
13871 if (fabs(newskew) > 0.0001) rotation = newskew;
13872 }
13873
13874 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
13875
13876 UpdateGPSCompassStatusBox(true); // Pick up the rotation
13877 }
13878
13879 // refresh Piano
13880 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
13881 if (idx < 0) return;
13882
13883 std::vector<int> piano_active_chart_index_array;
13884 piano_active_chart_index_array.push_back(
13885 GetpCurrentStack()->GetCurrentEntrydbIndex());
13886 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
13887}
13888
13889void ChartCanvas::SelectdbChart(int dbindex) {
13890 if (!GetpCurrentStack()) return;
13891 if (!ChartData) return;
13892
13893 if (dbindex >= 0) {
13894 // Open the new chart
13895 ChartBase *pTentative_Chart;
13896 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
13897
13898 if (pTentative_Chart) {
13899 if (m_singleChart) m_singleChart->Deactivate();
13900
13901 m_singleChart = pTentative_Chart;
13902 m_singleChart->Activate();
13903
13904 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13905 GetpCurrentStack(), m_singleChart->GetFullPath());
13906 }
13907
13908 // Setup the view
13909 double zLat, zLon;
13910 if (m_bFollow) {
13911 zLat = gLat;
13912 zLon = gLon;
13913 } else {
13914 zLat = m_vLat;
13915 zLon = m_vLon;
13916 }
13917
13918 double best_scale_ppm = GetBestVPScale(m_singleChart);
13919
13920 if (m_singleChart)
13921 SetViewPoint(zLat, zLon, best_scale_ppm,
13922 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
13923
13924 // SetChartUpdatePeriod( );
13925
13926 // UpdateGPSCompassStatusBox(); // Pick up the rotation
13927 }
13928
13929 // TODO refresh_Piano();
13930}
13931
13932void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
13933 double target_scale = GetVP().view_scale_ppm;
13934
13935 if (!GetQuiltMode()) {
13936 if (GetpCurrentStack()) {
13937 int stack_index = -1;
13938 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
13939 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
13940 if (check_dbIndex < 0) continue;
13941 const ChartTableEntry &cte =
13942 ChartData->GetChartTableEntry(check_dbIndex);
13943 if (type == cte.GetChartType()) {
13944 stack_index = i;
13945 break;
13946 } else if (family == cte.GetChartFamily()) {
13947 stack_index = i;
13948 break;
13949 }
13950 }
13951
13952 if (stack_index >= 0) {
13953 SelectChartFromStack(stack_index);
13954 }
13955 }
13956 } else {
13957 int sel_dbIndex = -1;
13958 std::vector<int> piano_chart_index_array =
13959 GetQuiltExtendedStackdbIndexArray();
13960 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13961 int check_dbIndex = piano_chart_index_array[i];
13962 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13963 if (type == cte.GetChartType()) {
13964 if (IsChartQuiltableRef(check_dbIndex)) {
13965 sel_dbIndex = check_dbIndex;
13966 break;
13967 }
13968 } else if (family == cte.GetChartFamily()) {
13969 if (IsChartQuiltableRef(check_dbIndex)) {
13970 sel_dbIndex = check_dbIndex;
13971 break;
13972 }
13973 }
13974 }
13975
13976 if (sel_dbIndex >= 0) {
13977 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
13978 // Re-qualify the quilt reference chart selection
13979 AdjustQuiltRefChart();
13980 }
13981
13982 // Now reset the scale to the target...
13983 SetVPScale(target_scale);
13984 }
13985
13986 SetQuiltChartHiLiteIndex(-1);
13987
13988 ReloadVP();
13989}
13990
13991bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
13992 return std::find(m_tile_yesshow_index_array.begin(),
13993 m_tile_yesshow_index_array.end(),
13994 index) != m_tile_yesshow_index_array.end();
13995}
13996
13997bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
13998 return std::find(m_tile_noshow_index_array.begin(),
13999 m_tile_noshow_index_array.end(),
14000 index) != m_tile_noshow_index_array.end();
14001}
14002
14003void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14004 if (std::find(m_tile_noshow_index_array.begin(),
14005 m_tile_noshow_index_array.end(),
14006 index) == m_tile_noshow_index_array.end()) {
14007 m_tile_noshow_index_array.push_back(index);
14008 }
14009}
14010
14011//-------------------------------------------------------------------------------------------------------
14012//
14013// Piano support
14014//
14015//-------------------------------------------------------------------------------------------------------
14016
14017void ChartCanvas::HandlePianoClick(
14018 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14019 if (g_options && g_options->IsShown())
14020 return; // Piano might be invalid due to chartset updates.
14021 if (!m_pCurrentStack) return;
14022 if (!ChartData) return;
14023
14024 // stop movement or on slow computer we may get something like :
14025 // zoom out with the wheel (timer is set)
14026 // quickly click and display a chart, which may zoom in
14027 // but the delayed timer fires first and it zooms out again!
14028 StopMovement();
14029
14030 // When switching by piano key click, we may appoint the new target chart to
14031 // be any chart in the composite array.
14032 // As an improvement to UX, find the chart that is "closest" to the current
14033 // vp,
14034 // and select that chart. This will cause a jump to the centroid of that
14035 // chart
14036
14037 double distance = 25000; // RTW
14038 int closest_index = -1;
14039 for (int chart_index : selected_dbIndex_array) {
14040 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14041 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14042 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14043
14044 // measure distance as Manhattan style
14045 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14046 if (test_distance < distance) {
14047 distance = test_distance;
14048 closest_index = chart_index;
14049 }
14050 }
14051
14052 int selected_dbIndex = selected_dbIndex_array[0];
14053 if (closest_index >= 0) selected_dbIndex = closest_index;
14054
14055 if (!GetQuiltMode()) {
14056 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14057 if (IsChartQuiltableRef(selected_dbIndex)) {
14058 ToggleCanvasQuiltMode();
14059 SelectQuiltRefdbChart(selected_dbIndex);
14060 m_bpersistent_quilt = false;
14061 } else {
14062 SelectChartFromStack(selected_index);
14063 }
14064 } else {
14065 SelectChartFromStack(selected_index);
14066 g_sticky_chart = selected_dbIndex;
14067 }
14068
14069 if (m_singleChart)
14070 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14071 } else {
14072 // Handle MBTiles overlays first
14073 // Left click simply toggles the noshow array index entry
14074 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14075 bool bfound = false;
14076 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14077 if (m_tile_noshow_index_array[i] ==
14078 selected_dbIndex) { // chart is in the noshow list
14079 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14080 i); // erase it
14081 bfound = true;
14082 break;
14083 }
14084 }
14085 if (!bfound) {
14086 m_tile_noshow_index_array.push_back(selected_dbIndex);
14087 }
14088
14089 // If not already present, add this tileset to the "yes_show" array.
14090 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14091 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14092 }
14093
14094 else {
14095 if (IsChartQuiltableRef(selected_dbIndex)) {
14096 // if( ChartData ) ChartData->PurgeCache();
14097
14098 // If the chart is a vector chart, and of very large scale,
14099 // then we had better set the new scale directly to avoid excessive
14100 // underzoom on, eg, Inland ENCs
14101 bool set_scale = false;
14102 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14103 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14104 set_scale = true;
14105 }
14106 }
14107
14108 if (!set_scale) {
14109 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14110 } else {
14111 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14112
14113 // Adjust scale so that the selected chart is underzoomed/overzoomed
14114 // by a controlled amount
14115 ChartBase *pc =
14116 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14117 if (pc) {
14118 double proposed_scale_onscreen =
14120
14121 if (g_bPreserveScaleOnX) {
14122 proposed_scale_onscreen =
14123 wxMin(proposed_scale_onscreen,
14124 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14125 GetCanvasWidth()));
14126 } else {
14127 proposed_scale_onscreen =
14128 wxMin(proposed_scale_onscreen,
14129 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14130 GetCanvasWidth()));
14131
14132 proposed_scale_onscreen =
14133 wxMax(proposed_scale_onscreen,
14134 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14135 g_b_overzoom_x));
14136 }
14137
14138 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14139 }
14140 }
14141 } else {
14142 ToggleCanvasQuiltMode();
14143 SelectdbChart(selected_dbIndex);
14144 m_bpersistent_quilt = true;
14145 }
14146 }
14147 }
14148
14149 SetQuiltChartHiLiteIndex(-1);
14150 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14151 // (checkmarks etc)
14152 HideChartInfoWindow();
14153 DoCanvasUpdate();
14154 ReloadVP(); // Pick up the new selections
14155}
14156
14157void ChartCanvas::HandlePianoRClick(
14158 int x, int y, int selected_index,
14159 const std::vector<int> &selected_dbIndex_array) {
14160 if (g_options && g_options->IsShown())
14161 return; // Piano might be invalid due to chartset updates.
14162 if (!GetpCurrentStack()) return;
14163
14164 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14165 UpdateCanvasControlBar();
14166
14167 SetQuiltChartHiLiteIndex(-1);
14168}
14169
14170void ChartCanvas::HandlePianoRollover(
14171 int selected_index, const std::vector<int> &selected_dbIndex_array,
14172 int n_charts, int scale) {
14173 if (g_options && g_options->IsShown())
14174 return; // Piano might be invalid due to chartset updates.
14175 if (!GetpCurrentStack()) return;
14176 if (!ChartData) return;
14177
14178 if (ChartData->IsBusy()) return;
14179
14180 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14181
14182 if (!GetQuiltMode()) {
14183 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14184 } else {
14185 // Select the correct vector
14186 std::vector<int> piano_chart_index_array;
14187 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14188 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14189 if ((GetpCurrentStack()->nEntry > 1) ||
14190 (piano_chart_index_array.size() >= 1)) {
14191 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14192
14193 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14194 ReloadVP(false); // no VP adjustment allowed
14195 } else if (GetpCurrentStack()->nEntry == 1) {
14196 const ChartTableEntry &cte =
14197 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14198 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14199 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14200 ReloadVP(false);
14201 } else if ((-1 == selected_index) &&
14202 (0 == selected_dbIndex_array.size())) {
14203 ShowChartInfoWindow(key_location.x, -1);
14204 }
14205 }
14206 } else {
14207 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14208
14209 if ((GetpCurrentStack()->nEntry > 1) ||
14210 (piano_chart_index_array.size() >= 1)) {
14211 if (n_charts > 1)
14212 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14213 selected_dbIndex_array);
14214 else if (n_charts == 1)
14215 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14216
14217 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14218 ReloadVP(false); // no VP adjustment allowed
14219 }
14220 }
14221 }
14222}
14223
14224void ChartCanvas::ClearPianoRollover() {
14225 ClearQuiltChartHiLiteIndexArray();
14226 ShowChartInfoWindow(0, -1);
14227 std::vector<int> vec;
14228 ShowCompositeInfoWindow(0, 0, 0, vec);
14229 ReloadVP(false);
14230}
14231
14232void ChartCanvas::UpdateCanvasControlBar(void) {
14233 if (m_pianoFrozen) return;
14234
14235 if (!GetpCurrentStack()) return;
14236 if (!ChartData) return;
14237 if (!g_bShowChartBar) return;
14238
14239 int sel_type = -1;
14240 int sel_family = -1;
14241
14242 std::vector<int> piano_chart_index_array;
14243 std::vector<int> empty_piano_chart_index_array;
14244
14245 wxString old_hash = m_Piano->GetStoredHash();
14246
14247 if (GetQuiltMode()) {
14248 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14249 GetQuiltFullScreendbIndexArray());
14250
14251 std::vector<int> piano_active_chart_index_array =
14252 GetQuiltCandidatedbIndexArray();
14253 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14254
14255 std::vector<int> piano_eclipsed_chart_index_array =
14256 GetQuiltEclipsedStackdbIndexArray();
14257 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14258
14259 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14260 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14261
14262 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14263 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14264 } else {
14265 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14266 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14267 // TODO refresh_Piano();
14268
14269 if (m_singleChart) {
14270 sel_type = m_singleChart->GetChartType();
14271 sel_family = m_singleChart->GetChartFamily();
14272 }
14273 }
14274
14275 // Set up the TMerc and Skew arrays
14276 std::vector<int> piano_skew_chart_index_array;
14277 std::vector<int> piano_tmerc_chart_index_array;
14278 std::vector<int> piano_poly_chart_index_array;
14279
14280 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14281 const ChartTableEntry &ctei =
14282 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14283 double skew_norm = ctei.GetChartSkew();
14284 if (skew_norm > 180.) skew_norm -= 360.;
14285
14286 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14287 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14288
14289 // Polyconic skewed charts should show as skewed
14290 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14291 if (fabs(skew_norm) > 1.)
14292 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14293 else
14294 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14295 } else if (fabs(skew_norm) > 1.)
14296 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14297 }
14298 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14299 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14300 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14301
14302 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14303 if (new_hash != old_hash) {
14304 m_Piano->FormatKeys();
14305 HideChartInfoWindow();
14306 m_Piano->ResetRollover();
14307 SetQuiltChartHiLiteIndex(-1);
14308 m_brepaint_piano = true;
14309 }
14310
14311 // Create a bitmask int that describes what Family/Type of charts are shown in
14312 // the bar, and notify the platform.
14313 int mask = 0;
14314 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14315 const ChartTableEntry &ctei =
14316 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14317 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14318 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14319 if (e == CHART_FAMILY_RASTER) mask |= 1;
14320 if (e == CHART_FAMILY_VECTOR) {
14321 if (t == CHART_TYPE_CM93COMP)
14322 mask |= 4;
14323 else
14324 mask |= 2;
14325 }
14326 }
14327
14328 wxString s_indicated;
14329 if (sel_type == CHART_TYPE_CM93COMP)
14330 s_indicated = _T("cm93");
14331 else {
14332 if (sel_family == CHART_FAMILY_RASTER)
14333 s_indicated = _T("raster");
14334 else if (sel_family == CHART_FAMILY_VECTOR)
14335 s_indicated = _T("vector");
14336 }
14337
14338 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14339}
14340
14341void ChartCanvas::FormatPianoKeys(void) { m_Piano->FormatKeys(); }
14342
14343void ChartCanvas::PianoPopupMenu(
14344 int x, int y, int selected_index,
14345 const std::vector<int> &selected_dbIndex_array) {
14346 if (!GetpCurrentStack()) return;
14347
14348 // No context menu if quilting is disabled
14349 if (!GetQuiltMode()) return;
14350
14351 m_piano_ctx_menu = new wxMenu();
14352
14353 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14354 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14355 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14356 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14357 } else {
14358 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14359 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14360 // wxEVT_COMMAND_MENU_SELECTED,
14361 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14362
14363 menu_selected_dbIndex = selected_dbIndex_array[0];
14364 menu_selected_index = selected_index;
14365
14366 // Search the no-show array
14367 bool b_is_in_noshow = false;
14368 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14369 if (m_quilt_noshow_index_array[i] ==
14370 menu_selected_dbIndex) // chart is in the noshow list
14371 {
14372 b_is_in_noshow = true;
14373 break;
14374 }
14375 }
14376
14377 if (b_is_in_noshow) {
14378 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14379 _("Show This Chart"));
14380 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14381 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14382 } else if (GetpCurrentStack()->nEntry > 1) {
14383 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14384 _("Hide This Chart"));
14385 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14386 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14387 }
14388 }
14389
14390 wxPoint pos = wxPoint(x, y - 30);
14391
14392 // Invoke the drop-down menu
14393 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14394 PopupMenu(m_piano_ctx_menu, pos);
14395
14396 delete m_piano_ctx_menu;
14397 m_piano_ctx_menu = NULL;
14398
14399 HideChartInfoWindow();
14400 m_Piano->ResetRollover();
14401
14402 SetQuiltChartHiLiteIndex(-1);
14403 ClearQuiltChartHiLiteIndexArray();
14404
14405 ReloadVP();
14406}
14407
14408void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14409 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14410 if (m_quilt_noshow_index_array[i] ==
14411 menu_selected_dbIndex) // chart is in the noshow list
14412 {
14413 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14414 break;
14415 }
14416 }
14417}
14418
14419void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14420 if (!GetpCurrentStack()) return;
14421 if (!ChartData) return;
14422
14423 RemoveChartFromQuilt(menu_selected_dbIndex);
14424
14425 // It could happen that the chart being disabled is the reference
14426 // chart....
14427 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14428 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14429
14430 int i = menu_selected_index + 1; // select next smaller scale chart
14431 bool b_success = false;
14432 while (i < GetpCurrentStack()->nEntry - 1) {
14433 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14434 if (type == ChartData->GetDBChartType(dbIndex)) {
14435 SelectQuiltRefChart(i);
14436 b_success = true;
14437 break;
14438 }
14439 i++;
14440 }
14441
14442 // If that did not work, try to select the next larger scale compatible
14443 // chart
14444 if (!b_success) {
14445 i = menu_selected_index - 1;
14446 while (i > 0) {
14447 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14448 if (type == ChartData->GetDBChartType(dbIndex)) {
14449 SelectQuiltRefChart(i);
14450 b_success = true;
14451 break;
14452 }
14453 i--;
14454 }
14455 }
14456 }
14457}
14458
14459void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14460 // Remove the item from the list (if it appears) to avoid multiple addition
14461 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14462 if (m_quilt_noshow_index_array[i] ==
14463 dbIndex) // chart is already in the noshow list
14464 {
14465 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14466 break;
14467 }
14468 }
14469
14470 m_quilt_noshow_index_array.push_back(dbIndex);
14471}
14472
14473bool ChartCanvas::UpdateS52State() {
14474 bool retval = false;
14475 // printf(" update %d\n", IsPrimaryCanvas());
14476
14477 if (ps52plib) {
14478 ps52plib->SetShowS57Text(m_encShowText);
14479 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14480 ps52plib->m_bShowSoundg = m_encShowDepth;
14481 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14482 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14483
14484 // Lights
14485 if (!m_encShowLights) // On, going off
14486 ps52plib->AddObjNoshow("LIGHTS");
14487 else // Off, going on
14488 ps52plib->RemoveObjNoshow("LIGHTS");
14489 ps52plib->SetLightsOff(!m_encShowLights);
14490 ps52plib->m_bExtendLightSectors = true;
14491
14492 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14493 ps52plib->SetAnchorOn(m_encShowAnchor);
14494 ps52plib->SetQualityOfData(m_encShowDataQual);
14495 }
14496
14497 return retval;
14498}
14499
14500void ChartCanvas::SetShowENCDataQual(bool show) {
14501 m_encShowDataQual = show;
14502 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14503 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14504
14505 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14506}
14507
14508void ChartCanvas::SetShowENCText(bool show) {
14509 m_encShowText = show;
14510 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14511 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14512
14513 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14514}
14515
14516void ChartCanvas::SetENCDisplayCategory(int category) {
14517 m_encDisplayCategory = category;
14518 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14519}
14520
14521void ChartCanvas::SetShowENCDepth(bool show) {
14522 m_encShowDepth = show;
14523 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14524 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14525
14526 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14527}
14528
14529void ChartCanvas::SetShowENCLightDesc(bool show) {
14530 m_encShowLightDesc = show;
14531 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14532 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14533
14534 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14535}
14536
14537void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14538 m_encShowBuoyLabels = show;
14539 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14540}
14541
14542void ChartCanvas::SetShowENCLights(bool show) {
14543 m_encShowLights = show;
14544 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14545 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14546
14547 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14548}
14549
14550void ChartCanvas::SetShowENCAnchor(bool show) {
14551 m_encShowAnchor = show;
14552 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14553 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14554
14555 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14556}
14557
14558wxRect ChartCanvas::GetMUIBarRect() {
14559 wxRect rv;
14560 if (m_muiBar) {
14561 rv = m_muiBar->GetRect();
14562 }
14563
14564 return rv;
14565}
14566
14567void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14568 if (!GetAlertString().IsEmpty()) {
14569 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14570 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14571
14572 dc.SetFont(*pfont);
14573 dc.SetPen(*wxTRANSPARENT_PEN);
14574
14575 dc.SetBrush(wxColour(243, 229, 47));
14576 int w, h;
14577 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14578 h += 2;
14579 // int yp = vp.pix_height - 20 - h;
14580
14581 wxRect sbr = GetScaleBarRect();
14582 int xp = sbr.x + sbr.width + 10;
14583 int yp = (sbr.y + sbr.height) - h;
14584
14585 int wdraw = w + 10;
14586 dc.DrawRectangle(xp, yp, wdraw, h);
14587 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14588 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14589 }
14590}
14591
14592//--------------------------------------------------------------------------------------------------------
14593// Screen Brightness Control Support Routines
14594//
14595//--------------------------------------------------------------------------------------------------------
14596
14597#ifdef __UNIX__
14598#define BRIGHT_XCALIB
14599#define __OPCPN_USEICC__
14600#endif
14601
14602#ifdef __OPCPN_USEICC__
14603int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14604 double co_green, double co_blue);
14605
14606wxString temp_file_name;
14607#endif
14608
14609#if 0
14610class ocpnCurtain: public wxDialog
14611{
14612 DECLARE_CLASS( ocpnCurtain )
14613 DECLARE_EVENT_TABLE()
14614
14615public:
14616 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14617 ~ocpnCurtain( );
14618 bool ProcessEvent(wxEvent& event);
14619
14620};
14621
14622IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14623
14624BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14625END_EVENT_TABLE()
14626
14627ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14628{
14629 wxDialog::Create( parent, -1, _T("ocpnCurtain"), position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14630}
14631
14632ocpnCurtain::~ocpnCurtain()
14633{
14634}
14635
14636bool ocpnCurtain::ProcessEvent(wxEvent& event)
14637{
14638 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14639 return GetParent()->GetEventHandler()->ProcessEvent(event);
14640}
14641#endif
14642
14643#ifdef _WIN32
14644#include <windows.h>
14645
14646HMODULE hGDI32DLL;
14647typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14648typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14649SetDeviceGammaRamp_ptr_type
14650 g_pSetDeviceGammaRamp; // the API entry points in the dll
14651GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14652
14653WORD *g_pSavedGammaMap;
14654
14655#endif
14656
14657int InitScreenBrightness(void) {
14658#ifdef _WIN32
14659#ifdef ocpnUSE_GL
14660 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14661 HDC hDC;
14662 BOOL bbr;
14663
14664 if (NULL == hGDI32DLL) {
14665 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14666
14667 if (NULL != hGDI32DLL) {
14668 // Get the entry points of the required functions
14669 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14670 hGDI32DLL, "SetDeviceGammaRamp");
14671 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14672 hGDI32DLL, "GetDeviceGammaRamp");
14673
14674 // If the functions are not found, unload the DLL and return false
14675 if ((NULL == g_pSetDeviceGammaRamp) ||
14676 (NULL == g_pGetDeviceGammaRamp)) {
14677 FreeLibrary(hGDI32DLL);
14678 hGDI32DLL = NULL;
14679 return 0;
14680 }
14681 }
14682 }
14683
14684 // Interface is ready, so....
14685 // Get some storage
14686 if (!g_pSavedGammaMap) {
14687 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14688
14689 hDC = GetDC(NULL); // Get the full screen DC
14690 bbr = g_pGetDeviceGammaRamp(
14691 hDC, g_pSavedGammaMap); // Get the existing ramp table
14692 ReleaseDC(NULL, hDC); // Release the DC
14693 }
14694
14695 // On Windows hosts, try to adjust the registry to allow full range
14696 // setting of Gamma table This is an undocumented Windows hack.....
14697 wxRegKey *pRegKey = new wxRegKey(
14698 _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows ")
14699 _T("NT\\CurrentVersion\\ICM"));
14700 if (!pRegKey->Exists()) pRegKey->Create();
14701 pRegKey->SetValue(_T("GdiIcmGammaRange"), 256);
14702
14703 g_brightness_init = true;
14704 return 1;
14705 }
14706#endif
14707
14708 {
14709 if (NULL == g_pcurtain) {
14710 if (gFrame->CanSetTransparent()) {
14711 // Build the curtain window
14712 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, _T(""),
14713 wxPoint(0, 0), ::wxGetDisplaySize(),
14714 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14715 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14716
14717 // g_pcurtain = new ocpnCurtain(gFrame,
14718 // wxPoint(0,0),::wxGetDisplaySize(),
14719 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14720 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14721
14722 g_pcurtain->Hide();
14723
14724 HWND hWnd = GetHwndOf(g_pcurtain);
14725 SetWindowLong(hWnd, GWL_EXSTYLE,
14726 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14727 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14728 g_pcurtain->SetTransparent(0);
14729
14730 g_pcurtain->Maximize();
14731 g_pcurtain->Show();
14732
14733 // All of this is obtuse, but necessary for Windows...
14734 g_pcurtain->Enable();
14735 g_pcurtain->Disable();
14736
14737 gFrame->Disable();
14738 gFrame->Enable();
14739 // SetFocus();
14740 }
14741 }
14742 g_brightness_init = true;
14743
14744 return 1;
14745 }
14746#else
14747 // Look for "xcalib" application
14748 wxString cmd(_T ( "xcalib -version" ));
14749
14750 wxArrayString output;
14751 long r = wxExecute(cmd, output);
14752 if (0 != r)
14753 wxLogMessage(
14754 _T(" External application \"xcalib\" not found. Screen brightness ")
14755 _T("not changed."));
14756
14757 g_brightness_init = true;
14758 return 0;
14759#endif
14760}
14761
14762int RestoreScreenBrightness(void) {
14763#ifdef _WIN32
14764
14765 if (g_pSavedGammaMap) {
14766 HDC hDC = GetDC(NULL); // Get the full screen DC
14767 g_pSetDeviceGammaRamp(hDC,
14768 g_pSavedGammaMap); // Restore the saved ramp table
14769 ReleaseDC(NULL, hDC); // Release the DC
14770
14771 free(g_pSavedGammaMap);
14772 g_pSavedGammaMap = NULL;
14773 }
14774
14775 if (g_pcurtain) {
14776 g_pcurtain->Close();
14777 g_pcurtain->Destroy();
14778 g_pcurtain = NULL;
14779 }
14780
14781 g_brightness_init = false;
14782 return 1;
14783
14784#endif
14785
14786#ifdef BRIGHT_XCALIB
14787 if (g_brightness_init) {
14788 wxString cmd;
14789 cmd = _T("xcalib -clear");
14790 wxExecute(cmd, wxEXEC_ASYNC);
14791 g_brightness_init = false;
14792 }
14793
14794 return 1;
14795#endif
14796
14797 return 0;
14798}
14799
14800// Set brightness. [0..100]
14801int SetScreenBrightness(int brightness) {
14802#ifdef _WIN32
14803
14804 // Under Windows, we use the SetDeviceGammaRamp function which exists in
14805 // some (most modern?) versions of gdi32.dll Load the required library dll,
14806 // if not already in place
14807#ifdef ocpnUSE_GL
14808 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14809 if (g_pcurtain) {
14810 g_pcurtain->Close();
14811 g_pcurtain->Destroy();
14812 g_pcurtain = NULL;
14813 }
14814
14815 InitScreenBrightness();
14816
14817 if (NULL == hGDI32DLL) {
14818 // Unicode stuff.....
14819 wchar_t wdll_name[80];
14820 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
14821 LPCWSTR cstr = wdll_name;
14822
14823 hGDI32DLL = LoadLibrary(cstr);
14824
14825 if (NULL != hGDI32DLL) {
14826 // Get the entry points of the required functions
14827 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14828 hGDI32DLL, "SetDeviceGammaRamp");
14829 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14830 hGDI32DLL, "GetDeviceGammaRamp");
14831
14832 // If the functions are not found, unload the DLL and return false
14833 if ((NULL == g_pSetDeviceGammaRamp) ||
14834 (NULL == g_pGetDeviceGammaRamp)) {
14835 FreeLibrary(hGDI32DLL);
14836 hGDI32DLL = NULL;
14837 return 0;
14838 }
14839 }
14840 }
14841
14842 HDC hDC = GetDC(NULL); // Get the full screen DC
14843
14844 /*
14845 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
14846 if (cmcap != CM_GAMMA_RAMP)
14847 {
14848 wxLogMessage(_T(" Video hardware does not support brightness control by
14849 gamma ramp adjustment.")); return false;
14850 }
14851 */
14852
14853 int increment = brightness * 256 / 100;
14854
14855 // Build the Gamma Ramp table
14856 WORD GammaTable[3][256];
14857
14858 int table_val = 0;
14859 for (int i = 0; i < 256; i++) {
14860 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
14861 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
14862 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
14863
14864 table_val += increment;
14865
14866 if (table_val > 65535) table_val = 65535;
14867 }
14868
14869 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
14870 ReleaseDC(NULL, hDC); // Release the DC
14871
14872 return 1;
14873 }
14874#endif
14875
14876 {
14877 if (g_pSavedGammaMap) {
14878 HDC hDC = GetDC(NULL); // Get the full screen DC
14879 g_pSetDeviceGammaRamp(hDC,
14880 g_pSavedGammaMap); // Restore the saved ramp table
14881 ReleaseDC(NULL, hDC); // Release the DC
14882 }
14883
14884 if (brightness < 100) {
14885 if (NULL == g_pcurtain) InitScreenBrightness();
14886
14887 if (g_pcurtain) {
14888 int sbrite = wxMax(1, brightness);
14889 sbrite = wxMin(100, sbrite);
14890
14891 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
14892 }
14893 } else {
14894 if (g_pcurtain) {
14895 g_pcurtain->Close();
14896 g_pcurtain->Destroy();
14897 g_pcurtain = NULL;
14898 }
14899 }
14900
14901 return 1;
14902 }
14903
14904#endif
14905
14906#ifdef BRIGHT_XCALIB
14907
14908 if (!g_brightness_init) {
14909 last_brightness = 100;
14910 g_brightness_init = true;
14911 temp_file_name = wxFileName::CreateTempFileName(_T(""));
14912 InitScreenBrightness();
14913 }
14914
14915#ifdef __OPCPN_USEICC__
14916 // Create a dead simple temporary ICC profile file, with gamma ramps set as
14917 // desired, and then activate this temporary profile using xcalib <filename>
14918 if (!CreateSimpleICCProfileFile(
14919 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
14920 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
14921 wxString cmd(_T ( "xcalib " ));
14922 cmd += temp_file_name;
14923
14924 wxExecute(cmd, wxEXEC_ASYNC);
14925 }
14926
14927#else
14928 // Or, use "xcalib -co" to set overall contrast value
14929 // This is not as nice, since the -co parameter wants to be a fraction of
14930 // the current contrast, and values greater than 100 are not allowed. As a
14931 // result, increases of contrast must do a "-clear" step first, which
14932 // produces objectionable flashing.
14933 if (brightness > last_brightness) {
14934 wxString cmd;
14935 cmd = _T("xcalib -clear");
14936 wxExecute(cmd, wxEXEC_ASYNC);
14937
14938 ::wxMilliSleep(10);
14939
14940 int brite_adj = wxMax(1, brightness);
14941 cmd.Printf(_T("xcalib -co %2d -a"), brite_adj);
14942 wxExecute(cmd, wxEXEC_ASYNC);
14943 } else {
14944 int brite_adj = wxMax(1, brightness);
14945 int factor = (brite_adj * 100) / last_brightness;
14946 factor = wxMax(1, factor);
14947 wxString cmd;
14948 cmd.Printf(_T("xcalib -co %2d -a"), factor);
14949 wxExecute(cmd, wxEXEC_ASYNC);
14950 }
14951
14952#endif
14953
14954 last_brightness = brightness;
14955
14956#endif
14957
14958 return 0;
14959}
14960
14961#ifdef __OPCPN_USEICC__
14962
14963#define MLUT_TAG 0x6d4c5554L
14964#define VCGT_TAG 0x76636774L
14965
14966int GetIntEndian(unsigned char *s) {
14967 int ret;
14968 unsigned char *p;
14969 int i;
14970
14971 p = (unsigned char *)&ret;
14972
14973 if (1)
14974 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
14975 else
14976 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
14977
14978 return ret;
14979}
14980
14981unsigned short GetShortEndian(unsigned char *s) {
14982 unsigned short ret;
14983 unsigned char *p;
14984 int i;
14985
14986 p = (unsigned char *)&ret;
14987
14988 if (1)
14989 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
14990 else
14991 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
14992
14993 return ret;
14994}
14995
14996// Create a very simple Gamma correction file readable by xcalib
14997int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14998 double co_green, double co_blue) {
14999 FILE *fp;
15000
15001 if (file_name) {
15002 fp = fopen(file_name, "wb");
15003 if (!fp) return -1; /* file can not be created */
15004 } else
15005 return -1; /* filename char pointer not valid */
15006
15007 // Write header
15008 char header[128];
15009 for (int i = 0; i < 128; i++) header[i] = 0;
15010
15011 fwrite(header, 128, 1, fp);
15012
15013 // Num tags
15014 int numTags0 = 1;
15015 int numTags = GetIntEndian((unsigned char *)&numTags0);
15016 fwrite(&numTags, 1, 4, fp);
15017
15018 int tagName0 = VCGT_TAG;
15019 int tagName = GetIntEndian((unsigned char *)&tagName0);
15020 fwrite(&tagName, 1, 4, fp);
15021
15022 int tagOffset0 = 128 + 4 * sizeof(int);
15023 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15024 fwrite(&tagOffset, 1, 4, fp);
15025
15026 int tagSize0 = 1;
15027 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15028 fwrite(&tagSize, 1, 4, fp);
15029
15030 fwrite(&tagName, 1, 4, fp); // another copy of tag
15031
15032 fwrite(&tagName, 1, 4, fp); // dummy
15033
15034 // Table type
15035
15036 /* VideoCardGammaTable (The simplest type) */
15037 int gammatype0 = 0;
15038 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15039 fwrite(&gammatype, 1, 4, fp);
15040
15041 int numChannels0 = 3;
15042 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15043 fwrite(&numChannels, 1, 2, fp);
15044
15045 int numEntries0 = 256;
15046 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15047 fwrite(&numEntries, 1, 2, fp);
15048
15049 int entrySize0 = 1;
15050 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15051 fwrite(&entrySize, 1, 2, fp);
15052
15053 unsigned char ramp[256];
15054
15055 // Red ramp
15056 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15057 fwrite(ramp, 256, 1, fp);
15058
15059 // Green ramp
15060 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15061 fwrite(ramp, 256, 1, fp);
15062
15063 // Blue ramp
15064 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15065 fwrite(ramp, 256, 1, fp);
15066
15067 fclose(fp);
15068
15069 return 0;
15070}
15071#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:4559
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4555
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:4505
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:742
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:473
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:504
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:7566
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5058
float GetVPScale()
Return the ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:462
double GetCanvasTrueScale()
Return the physical pixels per meter at chart center, accounting for latitude distortion.
Definition chcanv.h:478
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4636
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5337
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:726
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4580
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:4642
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4500
bool SetViewPoint(double lat, double lon)
Set the viewport center point.
Definition chcanv.cpp:5356
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:9906
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:450
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:186
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.
double GetDisplaySizeMM()
Get the width of the screen in millimeters.
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:1695
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:413
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:856
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