OpenCPN Partial API docs
Loading...
Searching...
No Matches
chcanv.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: Chart Canvas
5 * Author: David Register
6 *
7 ***************************************************************************
8 * Copyright (C) 2018 by David S. Register *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 **************************************************************************/
25
26// For compilers that support precompilation, includes "wx.h".
27#include <wx/wxprec.h>
28
29#ifndef WX_PRECOMP
30#include <wx/wx.h>
31#endif // precompiled headers
32#include <wx/image.h>
33#include <wx/graphics.h>
34#include <wx/clipbrd.h>
35#include <wx/aui/aui.h>
36
37#include "config.h"
38
39#include <wx/listimpl.cpp>
40
41#include "model/ais_decoder.h"
43#include "model/ais_target_data.h"
44#include "model/cmdline.h"
45#include "model/conn_params.h"
46#include "model/cutil.h"
47#include "model/geodesic.h"
48#include "model/gui.h"
49#include "model/idents.h"
50#include "model/multiplexer.h"
52#include "model/nav_object_database.h"
53#include "model/navobj_db.h"
54#include "model/navutil_base.h"
55#include "model/own_ship.h"
56#include "model/plugin_comm.h"
57#include "model/route.h"
58#include "model/routeman.h"
59#include "model/select.h"
60#include "model/select_item.h"
61#include "model/track.h"
62#include "model/wx28compat.h"
63
64#include "ais.h"
65#include "AISTargetAlertDialog.h"
66#include "CanvasConfig.h"
67#include "canvasMenu.h"
68#include "CanvasOptions.h"
69#include "chartdb.h"
70#include "chartimg.h"
71#include "chcanv.h"
72#include "ChInfoWin.h"
73#include "cm93.h" // for chart outline draw
74#include "compass.h"
75#include "concanv.h"
76#include "displays.h"
77#include "hotkeys_dlg.h"
78#include "FontMgr.h"
79#include "glTextureDescriptor.h"
80#include "gshhs.h"
81#include "iENCToolbar.h"
82#include "kml.h"
83#include "line_clip.h"
84#include "MarkInfo.h"
85#include "mbtiles.h"
86#include "MUIBar.h"
87#include "navutil.h"
88#include "OCPN_AUIManager.h"
89#include "ocpndc.h"
90#include "ocpn_frame.h"
91#include "ocpn_pixel.h"
92#include "OCPNRegion.h"
93#include "options.h"
94#include "piano.h"
95#include "pluginmanager.h"
96#include "Quilt.h"
97#include "route_gui.h"
98#include "routemanagerdialog.h"
99#include "route_point_gui.h"
100#include "route_validator.h"
101#include "RoutePropDlgImpl.h"
102#include "s52plib.h"
103#include "s52utils.h"
104#include "s57chart.h" // for ArrayOfS57Obj
105#include "SendToGpsDlg.h"
106#include "shapefile_basemap.h"
107#include "styles.h"
108#include "SystemCmdSound.h"
109#include "tcmgr.h"
110#include "TCWin.h"
111#include "thumbwin.h"
112#include "tide_time.h"
113#include "timers.h"
114#include "toolbar.h"
115#include "track_gui.h"
116#include "TrackPropDlg.h"
117#include "undo.h"
118
119#include "s57_ocpn_utils.h"
120
121#ifdef __ANDROID__
122#include "androidUTIL.h"
123#endif
124
125#ifdef ocpnUSE_GL
126#include "glChartCanvas.h"
127#include "notification_manager_gui.h"
129#endif
130
131#ifdef __VISUALC__
132#include <wx/msw/msvcrt.h>
133#endif
134
135#ifndef __WXMSW__
136#include <signal.h>
137#include <setjmp.h>
138
139#endif
140
141extern float g_ShipScaleFactorExp;
142extern double g_mouse_zoom_sensitivity;
143
144#include <vector>
145
146#ifdef __WXMSW__
147#define printf printf2
148
149int __cdecl printf2(const char *format, ...) {
150 char str[1024];
151
152 va_list argptr;
153 va_start(argptr, format);
154 int ret = vsnprintf(str, sizeof(str), format, argptr);
155 va_end(argptr);
156 OutputDebugStringA(str);
157 return ret;
158}
159#endif
160
161#if defined(__MSVC__) && (_MSC_VER < 1700)
162#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
163#endif
164
165// Define to enable the invocation of a temporary menubar by pressing the Alt
166// key. Not implemented for Windows XP, as it interferes with Alt-Tab
167// processing.
168#define OCPN_ALT_MENUBAR 1
169
170// Profiling support
171// #include "/usr/include/valgrind/callgrind.h"
172
173// ----------------------------------------------------------------------------
174// Useful Prototypes
175// ----------------------------------------------------------------------------
176extern bool G_FloatPtInPolygon(MyFlPoint *rgpts, int wnumpts, float x, float y);
177extern void catch_signals(int signo);
178
179extern void AlphaBlending(ocpnDC &dc, int x, int y, int size_x, int size_y,
180 float radius, wxColour color,
181 unsigned char transparency);
182
183extern double g_ChartNotRenderScaleFactor;
184extern ChartDB *ChartData;
185extern bool bDBUpdateInProgress;
186extern ColorScheme global_color_scheme;
187extern int g_nbrightness;
188
189extern ConsoleCanvas *console;
190extern OCPNPlatform *g_Platform;
191
192extern RouteList *pRouteList;
193extern std::vector<Track *> g_TrackList;
194extern MyConfig *pConfig;
195extern Routeman *g_pRouteMan;
196extern ThumbWin *pthumbwin;
197extern TCMgr *ptcmgr;
198extern Select *pSelectTC;
199extern MarkInfoDlg *g_pMarkInfoDialog;
200extern RoutePropDlgImpl *pRoutePropDialog;
201extern TrackPropDlg *pTrackPropDialog;
202extern ActiveTrack *g_pActiveTrack;
203
204extern double AnchorPointMinDist;
205extern bool AnchorAlertOn1;
206extern bool AnchorAlertOn2;
207extern int g_nAWMax;
208
209extern RouteManagerDialog *pRouteManagerDialog;
210extern GoToPositionDialog *pGoToPositionDialog;
211extern wxString GetLayerName(int id);
212extern wxString g_uploadConnection;
213extern bool g_bsimplifiedScalebar;
214
215extern bool bDrawCurrentValues;
216
217extern s52plib *ps52plib;
218
219extern bool g_bTempShowMenuBar;
220extern bool g_bShowMenuBar;
221extern bool g_bShowCompassWin;
222
223extern MyFrame *gFrame;
224extern options *g_options;
225
226extern int g_iNavAidRadarRingsNumberVisible;
227extern bool g_bNavAidRadarRingsShown;
228extern float g_fNavAidRadarRingsStep;
229extern int g_pNavAidRadarRingsStepUnits;
230extern bool g_bWayPointPreventDragging;
231extern bool g_bEnableZoomToCursor;
232extern bool g_bShowChartBar;
233extern int g_ENCSoundingScaleFactor;
234extern int g_ENCTextScaleFactor;
235extern int g_maxzoomin;
236
237bool g_bShowShipToActive;
238int g_shipToActiveStyle;
239int g_shipToActiveColor;
240
241extern AISTargetQueryDialog *g_pais_query_dialog_active;
242
243extern int g_S57_dialog_sx, g_S57_dialog_sy;
244
245extern PopUpDSlide *pPopupDetailSlider;
246extern int g_detailslider_dialog_x, g_detailslider_dialog_y;
247
248extern bool g_b_overzoom_x; // Allow high overzoom
249extern double g_plus_minus_zoom_factor;
250
251extern int g_OwnShipIconType;
252extern double g_n_ownship_length_meters;
253extern double g_n_ownship_beam_meters;
254extern double g_n_gps_antenna_offset_y;
255extern double g_n_gps_antenna_offset_x;
256extern int g_n_ownship_min_mm;
257
258extern double g_COGAvg; // only needed for debug....
259
260extern int g_click_stop;
261
262extern double g_ownship_predictor_minutes;
263extern int g_cog_predictor_style;
264extern wxString g_cog_predictor_color;
265extern int g_cog_predictor_endmarker;
266extern int g_ownship_HDTpredictor_style;
267extern wxString g_ownship_HDTpredictor_color;
268extern int g_ownship_HDTpredictor_endmarker;
269extern int g_ownship_HDTpredictor_width;
270extern double g_ownship_HDTpredictor_miles;
271
272extern bool g_bquiting;
273extern AISTargetListDialog *g_pAISTargetList;
274
275extern PlugInManager *g_pi_manager;
276
277extern OCPN_AUIManager *g_pauimgr;
278
279extern bool g_bopengl;
280
281extern bool g_bFullScreenQuilt;
282
283extern bool g_bsmoothpanzoom;
284extern bool g_bSmoothRecenter;
285
286bool g_bDebugOGL;
287
288extern bool g_b_assume_azerty;
289
290extern ChartGroupArray *g_pGroupArray;
291
292extern S57QueryDialog *g_pObjectQueryDialog;
293extern ocpnStyle::StyleManager *g_StyleManager;
294
295extern OcpnSound *g_anchorwatch_sound;
296
297extern bool g_bresponsive;
298extern int g_chart_zoom_modifier_raster;
299extern int g_chart_zoom_modifier_vector;
300extern int g_ChartScaleFactor;
301
302#ifdef ocpnUSE_GL
303#endif
304
305extern double g_gl_ms_per_frame;
306extern bool g_benable_rotate;
307extern bool g_bRollover;
308
309extern bool g_bSpaceDropMark;
310extern bool g_bAutoHideToolbar;
311extern int g_nAutoHideToolbar;
312extern bool g_bDeferredInitDone;
313
314extern wxString g_CmdSoundString;
315ShapeBaseChartSet gShapeBasemap;
316extern bool g_CanvasHideNotificationIcon;
317
318// TODO why are these static?
319
329static int mouse_x;
339static int mouse_y;
340static bool mouse_leftisdown;
341
342bool g_brouteCreating;
343
344bool g_bShowTrackPointTime;
345
346int r_gamma_mult;
347int g_gamma_mult;
348int b_gamma_mult;
349int gamma_state;
350bool g_brightness_init;
351int last_brightness;
352
353int g_cog_predictor_width;
354extern double g_display_size_mm;
355
356extern ocpnFloatingToolbarDialog *g_MainToolbar;
357extern iENCToolbar *g_iENCToolbar;
358extern wxColour g_colourOwnshipRangeRingsColour;
359
360// LIVE ETA OPTION
361bool g_bShowLiveETA;
362extern double g_defaultBoatSpeed;
363double g_defaultBoatSpeedUserUnit;
364
365extern int g_nAIS_activity_timer;
366extern bool g_bskew_comp;
367extern float g_compass_scalefactor;
368extern int g_COGAvgSec; // COG average period (sec.) for Course Up Mode
369extern bool g_btenhertz;
370
371wxGLContext *g_pGLcontext; // shared common context
372
373extern bool g_useMUI;
374extern unsigned int g_canvasConfig;
375
376extern ChartCanvas *g_focusCanvas;
377extern ChartCanvas *g_overlayCanvas;
378
379extern float g_toolbar_scalefactor;
380extern SENCThreadManager *g_SencThreadManager;
381
382wxString g_ObjQFileExt;
383
384// "Curtain" mode parameters
385wxDialog *g_pcurtain;
386
387extern int g_GUIScaleFactor;
388// Win DPI scale factor
389double g_scaler;
390wxString g_lastS52PLIBPluginMessage;
391extern bool g_bChartBarEx;
392bool g_PrintingInProgress;
393
394#define MIN_BRIGHT 10
395#define MAX_BRIGHT 100
396
397//------------------------------------------------------------------------------
398// ChartCanvas Implementation
399//------------------------------------------------------------------------------
400BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
401EVT_PAINT(ChartCanvas::OnPaint)
402EVT_ACTIVATE(ChartCanvas::OnActivate)
403EVT_SIZE(ChartCanvas::OnSize)
404#ifndef HAVE_WX_GESTURE_EVENTS
405EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
406#endif
407EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
408EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
409EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
410EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
411EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
412EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
413EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
414EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
415EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
416EVT_KEY_UP(ChartCanvas::OnKeyUp)
417EVT_CHAR(ChartCanvas::OnKeyChar)
418EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
419EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
420EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
421EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
422EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
423EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
424EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
425EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
426
427END_EVENT_TABLE()
428
429// Define a constructor for my canvas
430ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
431 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
432 m_nmea_log(nmea_log) {
433 parent_frame = (MyFrame *)frame; // save a pointer to parent
434 m_canvasIndex = canvasIndex;
435
436 pscratch_bm = NULL;
437
438 SetBackgroundColour(wxColour(0, 0, 0));
439 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
440 // color scheme change
441
442 m_groupIndex = 0;
443 m_bDrawingRoute = false;
444 m_bRouteEditing = false;
445 m_bMarkEditing = false;
446 m_bRoutePoinDragging = false;
447 m_bIsInRadius = false;
448 m_bMayToggleMenuBar = true;
449
450 m_bFollow = false;
451 m_bShowNavobjects = true;
452 m_bTCupdate = false;
453 m_bAppendingRoute = false; // was true in MSW, why??
454 pThumbDIBShow = NULL;
455 m_bShowCurrent = false;
456 m_bShowTide = false;
457 bShowingCurrent = false;
458 pCwin = NULL;
459 warp_flag = false;
460 m_bzooming = false;
461 m_b_paint_enable = true;
462 m_routeState = 0;
463
464 pss_overlay_bmp = NULL;
465 pss_overlay_mask = NULL;
466 m_bChartDragging = false;
467 m_bMeasure_Active = false;
468 m_bMeasure_DistCircle = false;
469 m_pMeasureRoute = NULL;
470 m_pTrackRolloverWin = NULL;
471 m_pRouteRolloverWin = NULL;
472 m_pAISRolloverWin = NULL;
473 m_bedge_pan = false;
474 m_disable_edge_pan = false;
475 m_dragoffsetSet = false;
476 m_bautofind = false;
477 m_bFirstAuto = true;
478 m_groupIndex = 0;
479 m_singleChart = NULL;
480 m_upMode = NORTH_UP_MODE;
481 m_bShowAIS = true;
482 m_bShowAISScaled = false;
483 m_timed_move_vp_active = false;
484
485 m_vLat = 0.;
486 m_vLon = 0.;
487
488 m_pCIWin = NULL;
489
490 m_pSelectedRoute = NULL;
491 m_pSelectedTrack = NULL;
492 m_pRoutePointEditTarget = NULL;
493 m_pFoundPoint = NULL;
494 m_pMouseRoute = NULL;
495 m_prev_pMousePoint = NULL;
496 m_pEditRouteArray = NULL;
497 m_pFoundRoutePoint = NULL;
498 m_FinishRouteOnKillFocus = true;
499
500 m_pRolloverRouteSeg = NULL;
501 m_pRolloverTrackSeg = NULL;
502 m_bsectors_shown = false;
503
504 m_bbrightdir = false;
505 r_gamma_mult = 1;
506 g_gamma_mult = 1;
507 b_gamma_mult = 1;
508
509 m_pos_image_user_day = NULL;
510 m_pos_image_user_dusk = NULL;
511 m_pos_image_user_night = NULL;
512 m_pos_image_user_grey_day = NULL;
513 m_pos_image_user_grey_dusk = NULL;
514 m_pos_image_user_grey_night = NULL;
515
516 m_zoom_factor = 1;
517 m_rotation_speed = 0;
518 m_mustmove = 0;
519
520 m_OSoffsetx = 0.;
521 m_OSoffsety = 0.;
522
523 m_pos_image_user_yellow_day = NULL;
524 m_pos_image_user_yellow_dusk = NULL;
525 m_pos_image_user_yellow_night = NULL;
526
527 SetOwnShipState(SHIP_INVALID);
528
529 undo = new Undo(this);
530
531 VPoint.Invalidate();
532
533 m_glcc = NULL;
534
535 m_focus_indicator_pix = 1;
536
537 m_pCurrentStack = NULL;
538 m_bpersistent_quilt = false;
539 m_piano_ctx_menu = NULL;
540 m_Compass = NULL;
541 m_NotificationsList = NULL;
542 m_notification_button = NULL;
543
544 g_ChartNotRenderScaleFactor = 2.0;
545 m_bShowScaleInStatusBar = true;
546
547 m_muiBar = NULL;
548 m_bShowScaleInStatusBar = false;
549 m_show_focus_bar = true;
550
551 m_bShowOutlines = false;
552 m_bDisplayGrid = false;
553 m_bShowDepthUnits = true;
554 m_encDisplayCategory = (int)STANDARD;
555
556 m_encShowLights = true;
557 m_encShowAnchor = true;
558 m_encShowDataQual = false;
559 m_bShowGPS = true;
560 m_pQuilt = new Quilt(this);
561 SetQuiltMode(true);
562 SetAlertString(_T(""));
563 m_sector_glat = 0;
564 m_sector_glon = 0;
565 g_PrintingInProgress = false;
566
567#ifdef HAVE_WX_GESTURE_EVENTS
568 m_oldVPSScale = -1.0;
569 m_popupWanted = false;
570 m_leftdown = false;
571#endif /* HAVE_WX_GESTURE_EVENTS */
572
573 SetupGlCanvas();
574
575 singleClickEventIsValid = false;
576
577 // Build the cursors
578
579 pCursorLeft = NULL;
580 pCursorRight = NULL;
581 pCursorUp = NULL;
582 pCursorDown = NULL;
583 pCursorArrow = NULL;
584 pCursorPencil = NULL;
585 pCursorCross = NULL;
586
587 RebuildCursors();
588
589 SetCursor(*pCursorArrow);
590
591 pPanTimer = new wxTimer(this, m_MouseDragging);
592 pPanTimer->Stop();
593
594 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
595 pMovementTimer->Stop();
596
597 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
598 pMovementStopTimer->Stop();
599
600 pRotDefTimer = new wxTimer(this, ROT_TIMER);
601 pRotDefTimer->Stop();
602
603 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
604 m_DoubleClickTimer->Stop();
605
606 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
607 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
608 m_chart_drag_inertia_active = false;
609
610 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
611 m_animationActive = false;
612
613 m_panx = m_pany = 0;
614 m_panspeed = 0;
615 m_panx_target_final = m_pany_target_final = 0;
616 m_panx_target_now = m_pany_target_now = 0;
617
618 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
619 pCurTrackTimer->Stop();
620 m_curtrack_timer_msec = 10;
621
622 m_wheelzoom_stop_oneshot = 0;
623 m_last_wheel_dir = 0;
624
625 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
626
627 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
628
629 m_rollover_popup_timer_msec = 20;
630
631 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
632
633 m_b_rot_hidef = true;
634
635 proute_bm = NULL;
636 m_prot_bm = NULL;
637
638 m_upMode = NORTH_UP_MODE;
639 m_bLookAhead = false;
640
641 // Set some benign initial values
642
643 m_cs = GLOBAL_COLOR_SCHEME_DAY;
644 VPoint.clat = 0;
645 VPoint.clon = 0;
646 VPoint.view_scale_ppm = 1;
647 VPoint.Invalidate();
648 m_nMeasureState = 0;
649
650 m_canvas_scale_factor = 1.;
651
652 m_canvas_width = 1000;
653
654 m_overzoomTextWidth = 0;
655 m_overzoomTextHeight = 0;
656
657 // Create the default world chart
658 pWorldBackgroundChart = new GSHHSChart;
659 gShapeBasemap.Reset();
660
661 // Create the default depth unit emboss maps
662 m_pEM_Feet = NULL;
663 m_pEM_Meters = NULL;
664 m_pEM_Fathoms = NULL;
665
666 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
667
668 m_pEM_OverZoom = NULL;
669 SetOverzoomFont();
670 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
671
672 // Build icons for tide/current points
673 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
674 m_bmTideDay = style->GetIconScaled(_T("tidesml"),
675 1. / g_Platform->GetDisplayDIPMult(this));
676
677 // Dusk
678 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
679
680 // Night
681 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
682
683 // Build Dusk/Night ownship icons
684 double factor_dusk = 0.5;
685 double factor_night = 0.25;
686
687 // Red
688 m_os_image_red_day = style->GetIcon(_T("ship-red")).ConvertToImage();
689
690 int rimg_width = m_os_image_red_day.GetWidth();
691 int rimg_height = m_os_image_red_day.GetHeight();
692
693 m_os_image_red_dusk = m_os_image_red_day.Copy();
694 m_os_image_red_night = m_os_image_red_day.Copy();
695
696 for (int iy = 0; iy < rimg_height; iy++) {
697 for (int ix = 0; ix < rimg_width; ix++) {
698 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
699 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
700 m_os_image_red_day.GetGreen(ix, iy),
701 m_os_image_red_day.GetBlue(ix, iy));
702 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
703 hsv.value = hsv.value * factor_dusk;
704 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
705 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
706
707 hsv = wxImage::RGBtoHSV(rgb);
708 hsv.value = hsv.value * factor_night;
709 nrgb = wxImage::HSVtoRGB(hsv);
710 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
711 }
712 }
713 }
714
715 // Grey
716 m_os_image_grey_day =
717 style->GetIcon(_T("ship-red")).ConvertToImage().ConvertToGreyscale();
718
719 int gimg_width = m_os_image_grey_day.GetWidth();
720 int gimg_height = m_os_image_grey_day.GetHeight();
721
722 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
723 m_os_image_grey_night = m_os_image_grey_day.Copy();
724
725 for (int iy = 0; iy < gimg_height; iy++) {
726 for (int ix = 0; ix < gimg_width; ix++) {
727 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
728 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
729 m_os_image_grey_day.GetGreen(ix, iy),
730 m_os_image_grey_day.GetBlue(ix, iy));
731 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
732 hsv.value = hsv.value * factor_dusk;
733 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
734 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
735
736 hsv = wxImage::RGBtoHSV(rgb);
737 hsv.value = hsv.value * factor_night;
738 nrgb = wxImage::HSVtoRGB(hsv);
739 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
740 }
741 }
742 }
743
744 // Yellow
745 m_os_image_yellow_day = m_os_image_red_day.Copy();
746
747 gimg_width = m_os_image_yellow_day.GetWidth();
748 gimg_height = m_os_image_yellow_day.GetHeight();
749
750 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
751 m_os_image_yellow_night = m_os_image_red_day.Copy();
752
753 for (int iy = 0; iy < gimg_height; iy++) {
754 for (int ix = 0; ix < gimg_width; ix++) {
755 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
756 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
757 m_os_image_yellow_day.GetGreen(ix, iy),
758 m_os_image_yellow_day.GetBlue(ix, iy));
759 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
760 hsv.hue += 60. / 360.; // shift to yellow
761 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
762 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
763
764 hsv = wxImage::RGBtoHSV(rgb);
765 hsv.value = hsv.value * factor_dusk;
766 hsv.hue += 60. / 360.; // shift to yellow
767 nrgb = wxImage::HSVtoRGB(hsv);
768 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
769
770 hsv = wxImage::RGBtoHSV(rgb);
771 hsv.hue += 60. / 360.; // shift to yellow
772 hsv.value = hsv.value * factor_night;
773 nrgb = wxImage::HSVtoRGB(hsv);
774 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
775 }
776 }
777 }
778
779 // Set initial pointers to ownship images
780 m_pos_image_red = &m_os_image_red_day;
781 m_pos_image_yellow = &m_os_image_yellow_day;
782 m_pos_image_grey = &m_os_image_grey_day;
783
784 SetUserOwnship();
785
786 m_pBrightPopup = NULL;
787
788#ifdef ocpnUSE_GL
789 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
790#endif
791
792 SetupGridFont();
793
794 m_Piano = new Piano(this);
795
796 m_bShowCompassWin = true;
797 m_Compass = new ocpnCompass(this);
798 m_Compass->SetScaleFactor(g_compass_scalefactor);
799 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
800
801 m_notification_button = new NotificationButton(this);
802 m_notification_button->SetScaleFactor(g_compass_scalefactor);
803 m_notification_button->Show(true);
804
805 m_pianoFrozen = false;
806
807 SetMinSize(wxSize(200, 200));
808
809 m_displayScale = 1.0;
810#if defined(__WXOSX__) || defined(__WXGTK3__)
811 // Support scaled HDPI displays.
812 m_displayScale = GetContentScaleFactor();
813#endif
814 VPoint.SetPixelScale(m_displayScale);
815
816#ifdef HAVE_WX_GESTURE_EVENTS
817 // if (!m_glcc)
818 {
819 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
820 wxLogError("Failed to enable touch events");
821 }
822
823 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
824
825 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
826 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
827
828 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
829 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
830
831 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
832 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
833
834 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
835 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
836 }
837#endif
838
839 // Listen for notification events
840 auto &noteman = NotificationManager::GetInstance();
841
842 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
843 evt_notificationlist_change_listener.Listen(
844 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
845 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
846 if (m_NotificationsList && m_NotificationsList->IsShown()) {
847 m_NotificationsList->ReloadNotificationList();
848 }
849 Refresh();
850 });
851}
852
853ChartCanvas::~ChartCanvas() {
854 delete pThumbDIBShow;
855
856 // Delete Cursors
857 delete pCursorLeft;
858 delete pCursorRight;
859 delete pCursorUp;
860 delete pCursorDown;
861 delete pCursorArrow;
862 delete pCursorPencil;
863 delete pCursorCross;
864
865 delete pPanTimer;
866 delete pMovementTimer;
867 delete pMovementStopTimer;
868 delete pCurTrackTimer;
869 delete pRotDefTimer;
870 delete m_DoubleClickTimer;
871
872 delete m_pTrackRolloverWin;
873 delete m_pRouteRolloverWin;
874 delete m_pAISRolloverWin;
875 delete m_pBrightPopup;
876
877 delete m_pCIWin;
878
879 delete pscratch_bm;
880
881 m_dc_route.SelectObject(wxNullBitmap);
882 delete proute_bm;
883
884 delete pWorldBackgroundChart;
885 delete pss_overlay_bmp;
886
887 delete m_pEM_Feet;
888 delete m_pEM_Meters;
889 delete m_pEM_Fathoms;
890
891 delete m_pEM_OverZoom;
892 // delete m_pEM_CM93Offset;
893
894 delete m_prot_bm;
895
896 delete m_pos_image_user_day;
897 delete m_pos_image_user_dusk;
898 delete m_pos_image_user_night;
899 delete m_pos_image_user_grey_day;
900 delete m_pos_image_user_grey_dusk;
901 delete m_pos_image_user_grey_night;
902 delete m_pos_image_user_yellow_day;
903 delete m_pos_image_user_yellow_dusk;
904 delete m_pos_image_user_yellow_night;
905
906 delete undo;
907#ifdef ocpnUSE_GL
908 if (!g_bdisable_opengl) {
909 delete m_glcc;
910
911#if wxCHECK_VERSION(2, 9, 0)
912 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
913#endif
914 }
915#endif
916
917 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
918 // wx tries to deliver events to this canvas during destroy.
919 MUIBar *muiBar = m_muiBar;
920 m_muiBar = 0;
921 delete muiBar;
922 delete m_pQuilt;
923 delete m_pCurrentStack;
924 delete m_Compass;
925 delete m_Piano;
926}
927
928void ChartCanvas::SetupGridFont() {
929 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
930 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
931 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
932 m_pgridFont = FontMgr::Get().FindOrCreateFont(
933 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
934 FALSE, wxString(_T ( "Arial" )));
935}
936
937void ChartCanvas::RebuildCursors() {
938 delete pCursorLeft;
939 delete pCursorRight;
940 delete pCursorUp;
941 delete pCursorDown;
942 delete pCursorArrow;
943 delete pCursorPencil;
944 delete pCursorCross;
945
946 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
947 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
948
949 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
950
951 wxImage ICursorLeft = style->GetIcon(_T("left")).ConvertToImage();
952 wxImage ICursorRight = style->GetIcon(_T("right")).ConvertToImage();
953 wxImage ICursorUp = style->GetIcon(_T("up")).ConvertToImage();
954 wxImage ICursorDown = style->GetIcon(_T("down")).ConvertToImage();
955 wxImage ICursorPencil =
956 style->GetIconScaled(_T("pencil"), pencilScale).ConvertToImage();
957 wxImage ICursorCross = style->GetIcon(_T("cross")).ConvertToImage();
958
959#if !defined(__WXMSW__) && !defined(__WXQT__)
960 ICursorLeft.ConvertAlphaToMask(128);
961 ICursorRight.ConvertAlphaToMask(128);
962 ICursorUp.ConvertAlphaToMask(128);
963 ICursorDown.ConvertAlphaToMask(128);
964 ICursorPencil.ConvertAlphaToMask(10);
965 ICursorCross.ConvertAlphaToMask(10);
966#endif
967
968 if (ICursorLeft.Ok()) {
969 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
970 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
971 pCursorLeft = new wxCursor(ICursorLeft);
972 } else
973 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
974
975 if (ICursorRight.Ok()) {
976 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
977 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
978 pCursorRight = new wxCursor(ICursorRight);
979 } else
980 pCursorRight = new wxCursor(wxCURSOR_ARROW);
981
982 if (ICursorUp.Ok()) {
983 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
984 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
985 pCursorUp = new wxCursor(ICursorUp);
986 } else
987 pCursorUp = new wxCursor(wxCURSOR_ARROW);
988
989 if (ICursorDown.Ok()) {
990 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
991 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
992 pCursorDown = new wxCursor(ICursorDown);
993 } else
994 pCursorDown = new wxCursor(wxCURSOR_ARROW);
995
996 if (ICursorPencil.Ok()) {
997 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
998 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
999 pCursorPencil = new wxCursor(ICursorPencil);
1000 } else
1001 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
1002
1003 if (ICursorCross.Ok()) {
1004 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
1005 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
1006 pCursorCross = new wxCursor(ICursorCross);
1007 } else
1008 pCursorCross = new wxCursor(wxCURSOR_ARROW);
1009
1010 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
1011 pPlugIn_Cursor = NULL;
1012}
1013
1014void ChartCanvas::CanvasApplyLocale() {
1015 CreateDepthUnitEmbossMaps(m_cs);
1016 CreateOZEmbossMapData(m_cs);
1017}
1018
1019void ChartCanvas::SetupGlCanvas() {
1020#ifndef __ANDROID__
1021#ifdef ocpnUSE_GL
1022 if (!g_bdisable_opengl) {
1023 if (g_bopengl) {
1024 wxLogMessage(_T("Creating glChartCanvas"));
1025 m_glcc = new glChartCanvas(this);
1026
1027 // We use one context for all GL windows, so that textures etc will be
1028 // automatically shared
1029 if (IsPrimaryCanvas()) {
1030 // qDebug() << "Creating Primary Context";
1031
1032 // wxGLContextAttrs ctxAttr;
1033 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
1034 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
1035 // NULL, &ctxAttr);
1036 wxGLContext *pctx = new wxGLContext(m_glcc);
1037 m_glcc->SetContext(pctx);
1038 g_pGLcontext = pctx; // Save a copy of the common context
1039 } else {
1040#ifdef __WXOSX__
1041 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
1042#else
1043 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
1044 // saved common context
1045#endif
1046 }
1047 }
1048 }
1049#endif
1050#endif
1051
1052#ifdef __ANDROID__ // ocpnUSE_GL
1053 if (!g_bdisable_opengl) {
1054 if (g_bopengl) {
1055 // qDebug() << "SetupGlCanvas";
1056 wxLogMessage(_T("Creating glChartCanvas"));
1057
1058 // We use one context for all GL windows, so that textures etc will be
1059 // automatically shared
1060 if (IsPrimaryCanvas()) {
1061 qDebug() << "Creating Primary glChartCanvas";
1062
1063 // wxGLContextAttrs ctxAttr;
1064 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
1065 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
1066 // NULL, &ctxAttr);
1067 m_glcc = new glChartCanvas(this);
1068
1069 wxGLContext *pctx = new wxGLContext(m_glcc);
1070 m_glcc->SetContext(pctx);
1071 g_pGLcontext = pctx; // Save a copy of the common context
1072 m_glcc->m_pParentCanvas = this;
1073 // m_glcc->Reparent(this);
1074 } else {
1075 qDebug() << "Creating Secondary glChartCanvas";
1076 // QGLContext *pctx =
1077 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
1078 // << "pctx: " << pctx;
1079
1080 m_glcc = new glChartCanvas(
1081 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
1082 // m_glcc = new glChartCanvas(this, pctx); //Shared
1083 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
1084 wxGLContext *pwxctx = new wxGLContext(m_glcc);
1085 m_glcc->SetContext(pwxctx);
1086 m_glcc->m_pParentCanvas = this;
1087 // m_glcc->Reparent(this);
1088 }
1089 }
1090 }
1091#endif
1092}
1093
1094void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
1095 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1096
1097 // On Android, we get a KillFocus on just about every keystroke.
1098 // Why?
1099#ifdef __ANDROID__
1100 return;
1101#endif
1102
1103 // Special logic:
1104 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
1105 // canvas focus. Why??? Who knows... So, we provide for this case by
1106 // starting a timer if required to actually Finish() a route on a legitimate
1107 // focus change, but not if the focus is quickly regained ( <20 msec.) on
1108 // this canvas.
1109#ifdef __WXOSX__
1110 if (m_routeState && m_FinishRouteOnKillFocus)
1111 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
1112#else
1113 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1114#endif
1115}
1116
1117void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
1118 m_routeFinishTimer.Stop();
1119
1120 // Try to keep the global top-line menubar selections up to date with the
1121 // current "focus" canvas
1122 gFrame->UpdateGlobalMenuItems(this);
1123
1124 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1125}
1126
1127void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
1128 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1129}
1130
1131#ifdef HAVE_WX_GESTURE_EVENTS
1132void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
1133 /* we defer the popup menu call upon the leftup event
1134 else the menu disappears immediately,
1135 (see
1136 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
1137 */
1138 m_popupWanted = true;
1139}
1140
1141void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
1142 // not implemented yet
1143}
1144
1145void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1146
1147void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1148
1149void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1150 wxPoint pos = event.GetPosition();
1151
1152 m_leftdown = false;
1153
1154 if (!m_popupWanted) {
1155 wxMouseEvent ev(wxEVT_LEFT_UP);
1156 ev.m_x = pos.x;
1157 ev.m_y = pos.y;
1158 MouseEvent(ev);
1159 return;
1160 }
1161
1162 m_popupWanted = false;
1163
1164 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1165 ev.m_x = pos.x;
1166 ev.m_y = pos.y;
1167
1168 MouseEvent(ev);
1169}
1170
1171void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1172 m_leftdown = true;
1173
1174 wxPoint pos = event.GetPosition();
1175 MouseEvent(event);
1176}
1177
1178void ChartCanvas::OnMotion(wxMouseEvent &event) {
1179 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1180 dragging, upon simple click, and without the OnLeftDown event before Thus,
1181 this consists in skiping it, and setting the leftdown bit according to a
1182 status that we trust */
1183 event.m_leftDown = m_leftdown;
1184 MouseEvent(event);
1185}
1186
1187void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1188 /* there are spurious end zoom events upon right-click */
1189 if (event.IsGestureEnd()) return;
1190
1191 double factor = event.GetZoomFactor();
1192
1193 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1194 m_oldVPSScale = GetVPScale();
1195 }
1196
1197 double current_vps = GetVPScale();
1198 double wanted_factor = m_oldVPSScale / current_vps * factor;
1199
1200 ZoomCanvas(wanted_factor, true, false);
1201
1202 // Allow combined zoom/pan operation
1203 if (event.IsGestureStart()) {
1204 m_zoomStartPoint = event.GetPosition();
1205 } else {
1206 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1207 PanCanvas(-delta.x, -delta.y);
1208 m_zoomStartPoint = event.GetPosition();
1209 }
1210}
1211
1212void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1213
1214void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1215 DoRotateCanvas(0.0);
1216}
1217#endif /* HAVE_WX_GESTURE_EVENTS */
1218
1219void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1220 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1221 m_vLat = pcc->iLat;
1222 m_vLon = pcc->iLon;
1223
1224 m_restore_dbindex = pcc->DBindex;
1225 m_bFollow = pcc->bFollow;
1226 if (pcc->GroupID < 0) pcc->GroupID = 0;
1227
1228 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1229 m_groupIndex = 0;
1230 else
1231 m_groupIndex = pcc->GroupID;
1232
1233 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1234
1235 ShowTides(pcc->bShowTides);
1236 ShowCurrents(pcc->bShowCurrents);
1237
1238 SetShowDepthUnits(pcc->bShowDepthUnits);
1239 SetShowGrid(pcc->bShowGrid);
1240 SetShowOutlines(pcc->bShowOutlines);
1241
1242 SetShowAIS(pcc->bShowAIS);
1243 SetAttenAIS(pcc->bAttenAIS);
1244
1245 // ENC options
1246 SetShowENCText(pcc->bShowENCText);
1247 m_encDisplayCategory = pcc->nENCDisplayCategory;
1248 m_encShowDepth = pcc->bShowENCDepths;
1249 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1250 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1251 m_encShowLights = pcc->bShowENCLights;
1252 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1253 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1254 m_encShowDataQual = pcc->bShowENCDataQuality;
1255
1256 bool courseUp = pcc->bCourseUp;
1257 bool headUp = pcc->bHeadUp;
1258 m_upMode = NORTH_UP_MODE;
1259 if (courseUp)
1260 m_upMode = COURSE_UP_MODE;
1261 else if (headUp)
1262 m_upMode = HEAD_UP_MODE;
1263
1264 m_bLookAhead = pcc->bLookahead;
1265
1266 m_singleChart = NULL;
1267}
1268
1269void ChartCanvas::ApplyGlobalSettings() {
1270 // GPS compas window
1271 if (m_Compass) {
1272 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1273 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1274 }
1275 m_notification_button->UpdateStatus();
1276}
1277
1278void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1279 bool groupOK = CheckGroup(m_groupIndex);
1280
1281 if (!groupOK) {
1282 SetGroupIndex(m_groupIndex, true);
1283 }
1284}
1285
1286void ChartCanvas::SetShowGPS(bool bshow) {
1287 if (m_bShowGPS != bshow) {
1288 delete m_Compass;
1289 m_Compass = new ocpnCompass(this, bshow);
1290 m_Compass->SetScaleFactor(g_compass_scalefactor);
1291 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1292 }
1293 m_bShowGPS = bshow;
1294}
1295
1296void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1297 m_bShowCompassWin = bshow;
1298 if (m_Compass) {
1299 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1300 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1301 }
1302}
1303
1304int ChartCanvas::GetPianoHeight() {
1305 int height = 0;
1306 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1307
1308 return height;
1309}
1310
1311void ChartCanvas::ConfigureChartBar() {
1312 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1313
1314 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
1315 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
1316
1317 if (GetQuiltMode()) {
1318 m_Piano->SetRoundedRectangles(true);
1319 }
1320 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
1321 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon(_T("polyprj"))));
1322 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
1323}
1324
1325void ChartCanvas::ShowTides(bool bShow) {
1326 gFrame->LoadHarmonics();
1327
1328 if (ptcmgr->IsReady()) {
1329 SetbShowTide(bShow);
1330
1331 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1332 } else {
1333 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1334 SetbShowTide(false);
1335 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1336 }
1337
1338 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1339 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1340
1341 // TODO
1342 // if( GetbShowTide() ) {
1343 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1344 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1345 // update
1346 // } else
1347 // FrameTCTimer.Stop();
1348}
1349
1350void ChartCanvas::ShowCurrents(bool bShow) {
1351 gFrame->LoadHarmonics();
1352
1353 if (ptcmgr->IsReady()) {
1354 SetbShowCurrent(bShow);
1355 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1356 } else {
1357 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1358 SetbShowCurrent(false);
1359 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1360 }
1361
1362 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1363 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1364
1365 // TODO
1366 // if( GetbShowCurrent() ) {
1367 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1368 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1369 // update
1370 // } else
1371 // FrameTCTimer.Stop();
1372}
1373
1374// TODO
1375extern bool g_bPreserveScaleOnX;
1376extern ChartDummy *pDummyChart;
1377extern int g_sticky_chart;
1378
1379void ChartCanvas::canvasRefreshGroupIndex(void) { SetGroupIndex(m_groupIndex); }
1380
1381void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1382 SetAlertString(_T(""));
1383
1384 int new_index = index;
1385 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1386
1387 bool bgroup_override = false;
1388 int old_group_index = new_index;
1389
1390 if (!CheckGroup(new_index)) {
1391 new_index = 0;
1392 bgroup_override = true;
1393 }
1394
1395 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1396 new_index = index;
1397
1398 // Get the currently displayed chart native scale, and the current ViewPort
1399 int current_chart_native_scale = GetCanvasChartNativeScale();
1400 ViewPort vp = GetVP();
1401
1402 m_groupIndex = new_index;
1403
1404 // Are there ENCs in this group
1405 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1406
1407 // Update the MUIBar for ENC availability
1408 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1409
1410 // Allow the chart database to pre-calculate the MBTile inclusion test
1411 // boolean...
1412 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1413
1414 // Invalidate the "sticky" chart on group change, since it might not be in
1415 // the new group
1416 g_sticky_chart = -1;
1417
1418 // We need a chartstack and quilt to figure out which chart to open in the
1419 // new group
1420 UpdateCanvasOnGroupChange();
1421
1422 int dbi_now = -1;
1423 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1424
1425 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1426
1427 // If a new reference chart is indicated, set a good scale for it.
1428 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1429 double best_scale = GetBestStartScale(dbi_hint, vp);
1430 SetVPScale(best_scale);
1431 }
1432
1433 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1434
1435 // Refresh the canvas, selecting the "best" chart,
1436 // applying the prior ViewPort exactly
1437 canvasChartsRefresh(dbi_hint);
1438
1439 UpdateCanvasControlBar();
1440
1441 if (!autoSwitch && bgroup_override) {
1442 // show a short timed message box
1443 wxString msg(_("Group \""));
1444
1445 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1446 msg += pGroup->m_group_name;
1447
1448 msg += _("\" is empty.");
1449
1450 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1451
1452 return;
1453 }
1454
1455 // Message box is deferred so that canvas refresh occurs properly before
1456 // dialog
1457 if (bgroup_override) {
1458 wxString msg(_("Group \""));
1459
1460 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1461 msg += pGroup->m_group_name;
1462
1463 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1464
1465 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1466 }
1467}
1468
1469bool ChartCanvas::CheckGroup(int igroup) {
1470 if (!ChartData) return true; // Not known yet...
1471
1472 if (igroup == 0) return true; // "all charts" is always OK
1473
1474 if (igroup < 0) // negative group is an error
1475 return false;
1476
1477 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1478
1479 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1480 // and auto-shift to group 0
1481 return false;
1482
1483 for (const auto &elem : pGroup->m_element_array) {
1484 for (unsigned int ic = 0;
1485 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1486 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1487 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1488
1489 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1490 }
1491 }
1492
1493 // If necessary, check for GSHHS
1494 for (const auto &elem : pGroup->m_element_array) {
1495 const wxString &element_root = elem.m_element_name;
1496 wxString test_string = _T("GSHH");
1497 if (element_root.Upper().Contains(test_string)) return true;
1498 }
1499
1500 return false;
1501}
1502
1503void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1504 if (!ChartData) return;
1505
1506 AbstractPlatform::ShowBusySpinner();
1507
1508 double old_scale = GetVPScale();
1509 InvalidateQuilt();
1510 SetQuiltRefChart(-1);
1511
1512 m_singleChart = NULL;
1513
1514 // delete m_pCurrentStack;
1515 // m_pCurrentStack = NULL;
1516
1517 // Build a new ChartStack
1518 if (!m_pCurrentStack) {
1519 m_pCurrentStack = new ChartStack;
1520 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1521 }
1522
1523 if (-1 != dbi_hint) {
1524 if (GetQuiltMode()) {
1525 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1526 SetQuiltRefChart(dbi_hint);
1527 } else {
1528 // Open the saved chart
1529 ChartBase *pTentative_Chart;
1530 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1531
1532 if (pTentative_Chart) {
1533 /* m_singleChart is always NULL here, (set above) should this go before
1534 * that? */
1535 if (m_singleChart) m_singleChart->Deactivate();
1536
1537 m_singleChart = pTentative_Chart;
1538 m_singleChart->Activate();
1539
1540 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1541 GetpCurrentStack(), m_singleChart->GetFullPath());
1542 }
1543 }
1544
1545 // refresh_Piano();
1546 } else {
1547 // Select reference chart from the stack, as though clicked by user
1548 // Make it the smallest scale chart on the stack
1549 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1550 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1551 SetQuiltRefChart(selected_index);
1552 }
1553
1554 // Validate the correct single chart, or set the quilt mode as appropriate
1555 SetupCanvasQuiltMode();
1556 if (!GetQuiltMode() && m_singleChart == 0) {
1557 // use a dummy like in DoChartUpdate
1558 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1559 m_singleChart = pDummyChart;
1560 SetVPScale(old_scale);
1561 }
1562
1563 ReloadVP();
1564
1565 UpdateCanvasControlBar();
1566 UpdateGPSCompassStatusBox(true);
1567
1568 SetCursor(wxCURSOR_ARROW);
1569
1570 AbstractPlatform::HideBusySpinner();
1571}
1572
1573bool ChartCanvas::DoCanvasUpdate(void) {
1574 double tLat, tLon; // Chart Stack location
1575 double vpLat, vpLon; // ViewPort location
1576 bool blong_jump = false;
1577 meters_to_shift = 0;
1578 dir_to_shift = 0;
1579
1580 bool bNewChart = false;
1581 bool bNewView = false;
1582 bool bCanvasChartAutoOpen = true; // debugging
1583
1584 bool bNewPiano = false;
1585 bool bOpenSpecified;
1586 ChartStack LastStack;
1587 ChartBase *pLast_Ch;
1588
1589 ChartStack WorkStack;
1590
1591 if (bDBUpdateInProgress) return false;
1592 if (!ChartData) return false;
1593
1594 if (ChartData->IsBusy()) return false;
1595
1596 // Startup case:
1597 // Quilting is enabled, but the last chart seen was not quiltable
1598 // In this case, drop to single chart mode, set persistence flag,
1599 // And open the specified chart
1600 // TODO implement this
1601 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1602 // if( GetQuiltMode() ) {
1603 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1604 // gFrame->ToggleQuiltMode();
1605 // m_bpersistent_quilt = true;
1606 // m_singleChart = NULL;
1607 // }
1608 // }
1609 // }
1610
1611 // If in auto-follow mode, use the current glat,glon to build chart
1612 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1613 // other means
1614
1615 if (m_bFollow) {
1616 tLat = gLat;
1617 tLon = gLon;
1618
1619 // Set the ViewPort center based on the OWNSHIP offset
1620 double dx = m_OSoffsetx;
1621 double dy = m_OSoffsety;
1622 double d_east = dx / GetVP().view_scale_ppm;
1623 double d_north = dy / GetVP().view_scale_ppm;
1624
1625 if (GetUpMode() == NORTH_UP_MODE) {
1626 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1627 } else {
1628 double offset_angle = atan2(d_north, d_east);
1629 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1630 double chart_angle = GetVPRotation();
1631 double target_angle = chart_angle + offset_angle;
1632 double d_east_mod = offset_distance * cos(target_angle);
1633 double d_north_mod = offset_distance * sin(target_angle);
1634 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1635 }
1636
1637 extern double gCog_gt;
1638
1639 // on lookahead mode, adjust the vp center point
1640 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1641 double cog_to_use = gCog;
1642 if (g_btenhertz &&
1643 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1644 cog_to_use = gCog_gt;
1645 blong_jump = true;
1646 }
1647 if (!g_btenhertz) cog_to_use = g_COGAvg;
1648
1649 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1650
1651 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1652 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1653
1654 double pixel_delta_tent =
1655 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1656
1657 double pixel_delta = 0;
1658
1659 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1660 // avoid jumping of the vp center point during slow maneuvering, or at
1661 // anchor....
1662 if (!std::isnan(gSog)) {
1663 if (gSog < 2.0)
1664 pixel_delta = 0.;
1665 else
1666 pixel_delta = pixel_delta_tent;
1667 }
1668
1669 meters_to_shift = 0;
1670 dir_to_shift = 0;
1671 if (!std::isnan(gCog)) {
1672 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1673 dir_to_shift = cog_to_use;
1674 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1675 &vpLon);
1676 } else {
1677 vpLat = gLat;
1678 vpLon = gLon;
1679 }
1680 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1681 m_OSoffsetx = 0; // center ownship on loss of GPS
1682 m_OSoffsety = 0;
1683 vpLat = gLat;
1684 vpLon = gLon;
1685 }
1686
1687 } else {
1688 tLat = m_vLat;
1689 tLon = m_vLon;
1690 vpLat = m_vLat;
1691 vpLon = m_vLon;
1692 }
1693
1694 if (GetQuiltMode()) {
1695 int current_db_index = -1;
1696 if (m_pCurrentStack)
1697 current_db_index =
1698 m_pCurrentStack
1699 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1700 // chart dbIndex
1701 else
1702 m_pCurrentStack = new ChartStack;
1703
1704 // This logic added to enable opening a chart when there is no
1705 // previous chart indication, either from inital startup, or from adding
1706 // new chart directory
1707 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1708 m_pCurrentStack) {
1709 if (m_pCurrentStack->nEntry) {
1710 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1711 1); // smallest scale
1712 SelectQuiltRefdbChart(new_dbIndex, true);
1713 m_bautofind = false;
1714 }
1715 }
1716
1717 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1718 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1719
1720 if (m_bFirstAuto) {
1721 // Allow the chart database to pre-calculate the MBTile inclusion test
1722 // boolean...
1723 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1724
1725 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1726 // physical pixels. On standard DPI displays where logical = physical
1727 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1728 // logical pixels, this ratio would be 0.5.
1729 double proposed_scale_onscreen =
1730 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1731
1732 int initial_db_index = m_restore_dbindex;
1733 if (initial_db_index < 0) {
1734 if (m_pCurrentStack->nEntry) {
1735 initial_db_index =
1736 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1737 } else
1738 m_bautofind = true; // initial_db_index = 0;
1739 }
1740
1741 if (m_pCurrentStack->nEntry) {
1742 int initial_type = ChartData->GetDBChartType(initial_db_index);
1743
1744 // Check to see if the target new chart is quiltable as a reference
1745 // chart
1746
1747 if (!IsChartQuiltableRef(initial_db_index)) {
1748 // If it is not quiltable, then walk the stack up looking for a
1749 // satisfactory chart i.e. one that is quiltable and of the same type
1750 // XXX if there's none?
1751 int stack_index = 0;
1752
1753 if (stack_index >= 0) {
1754 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1755 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1756 if (IsChartQuiltableRef(test_db_index) &&
1757 (initial_type ==
1758 ChartData->GetDBChartType(initial_db_index))) {
1759 initial_db_index = test_db_index;
1760 break;
1761 }
1762 stack_index++;
1763 }
1764 }
1765 }
1766
1767 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1768 if (pc) {
1769 SetQuiltRefChart(initial_db_index);
1770 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1771 }
1772 }
1773 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1774 // just GetVPScale(), so I'm not sure why it's necessary to define the
1775 // proposed_scale_onscreen variable.
1776 bNewView |= SetViewPoint(vpLat, vpLon,
1777 GetCanvasScaleFactor() / proposed_scale_onscreen,
1778 0, GetVPRotation());
1779 }
1780 // Measure rough jump distance if in bfollow mode
1781 // No good reason to do smooth pan for
1782 // jump distance more than one screen width at scale.
1783 bool super_jump = false;
1784 if (m_bFollow) {
1785 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1786 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1787 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1788 }
1789 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead) {
1790 int nstep = 5;
1791 if (blong_jump) nstep = 20;
1792 StartTimedMovementVP(vpLat, vpLon, nstep);
1793 } else {
1794 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1795 }
1796
1797 goto update_finish;
1798 }
1799
1800 // Single Chart Mode from here....
1801 pLast_Ch = m_singleChart;
1802 ChartTypeEnum new_open_type;
1803 ChartFamilyEnum new_open_family;
1804 if (pLast_Ch) {
1805 new_open_type = pLast_Ch->GetChartType();
1806 new_open_family = pLast_Ch->GetChartFamily();
1807 } else {
1808 new_open_type = CHART_TYPE_KAP;
1809 new_open_family = CHART_FAMILY_RASTER;
1810 }
1811
1812 bOpenSpecified = m_bFirstAuto;
1813
1814 // Make sure the target stack is valid
1815 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1816
1817 // Build a chart stack based on tLat, tLon
1818 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1819 m_groupIndex)) { // Bogus Lat, Lon?
1820 if (NULL == pDummyChart) {
1821 pDummyChart = new ChartDummy;
1822 bNewChart = true;
1823 }
1824
1825 if (m_singleChart)
1826 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1827
1828 m_singleChart = pDummyChart;
1829
1830 // If the current viewpoint is invalid, set the default scale to
1831 // something reasonable.
1832 double set_scale = GetVPScale();
1833 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1834
1835 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1836
1837 // If the chart stack has just changed, there is new status
1838 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1839 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1840 bNewPiano = true;
1841 bNewChart = true;
1842 }
1843 }
1844
1845 // Copy the new (by definition empty) stack into the target stack
1846 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1847
1848 goto update_finish;
1849 }
1850
1851 // Check to see if Chart Stack has changed
1852 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1853 // New chart stack, so...
1854 bNewPiano = true;
1855
1856 // Save a copy of the current stack
1857 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1858
1859 // Copy the new stack into the target stack
1860 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1861
1862 // Is Current Chart in new stack?
1863
1864 int tEntry = -1;
1865 if (NULL != m_singleChart) // this handles startup case
1866 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1867 m_singleChart->GetFullPath());
1868
1869 if (tEntry != -1) { // m_singleChart is in the new stack
1870 m_pCurrentStack->CurrentStackEntry = tEntry;
1871 bNewChart = false;
1872 }
1873
1874 else // m_singleChart is NOT in new stack
1875 { // So, need to open a new chart
1876 // Find the largest scale raster chart that opens OK
1877
1878 ChartBase *pProposed = NULL;
1879
1880 if (bCanvasChartAutoOpen) {
1881 bool search_direction =
1882 false; // default is to search from lowest to highest
1883 int start_index = 0;
1884
1885 // A special case: If panning at high scale, open largest scale
1886 // chart first
1887 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1888 (LastStack.nEntry == 0)) {
1889 search_direction = true;
1890 start_index = m_pCurrentStack->nEntry - 1;
1891 }
1892
1893 // Another special case, open specified index on program start
1894 if (bOpenSpecified) {
1895 search_direction = false;
1896 start_index = 0;
1897 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1898 start_index = 0;
1899
1900 new_open_type = CHART_TYPE_DONTCARE;
1901 }
1902
1903 pProposed = ChartData->OpenStackChartConditional(
1904 m_pCurrentStack, start_index, search_direction, new_open_type,
1905 new_open_family);
1906
1907 // Try to open other types/families of chart in some priority
1908 if (NULL == pProposed)
1909 pProposed = ChartData->OpenStackChartConditional(
1910 m_pCurrentStack, start_index, search_direction,
1911 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1912
1913 if (NULL == pProposed)
1914 pProposed = ChartData->OpenStackChartConditional(
1915 m_pCurrentStack, start_index, search_direction,
1916 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1917
1918 bNewChart = true;
1919
1920 } // bCanvasChartAutoOpen
1921
1922 else
1923 pProposed = NULL;
1924
1925 // If no go, then
1926 // Open a Dummy Chart
1927 if (NULL == pProposed) {
1928 if (NULL == pDummyChart) {
1929 pDummyChart = new ChartDummy;
1930 bNewChart = true;
1931 }
1932
1933 if (pLast_Ch)
1934 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1935
1936 pProposed = pDummyChart;
1937 }
1938
1939 // Arriving here, pProposed points to an opened chart, or NULL.
1940 if (m_singleChart) m_singleChart->Deactivate();
1941 m_singleChart = pProposed;
1942
1943 if (m_singleChart) {
1944 m_singleChart->Activate();
1945 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1946 m_pCurrentStack, m_singleChart->GetFullPath());
1947 }
1948 } // need new chart
1949
1950 // Arriving here, m_singleChart is opened and OK, or NULL
1951 if (NULL != m_singleChart) {
1952 // Setup the view using the current scale
1953 double set_scale = GetVPScale();
1954
1955 // If the current viewpoint is invalid, set the default scale to
1956 // something reasonable.
1957 if (!GetVP().IsValid())
1958 set_scale = 1. / 20000.;
1959 else { // otherwise, match scale if elected.
1960 double proposed_scale_onscreen;
1961
1962 if (m_bFollow) { // autoset the scale only if in autofollow
1963 double new_scale_ppm =
1964 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1965 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1966 } else
1967 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1968
1969 // This logic will bring a new chart onscreen at roughly twice the true
1970 // paper scale equivalent. Note that first chart opened on application
1971 // startup (bOpenSpecified = true) will open at the config saved scale
1972 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1973 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1974 double equivalent_vp_scale =
1975 GetCanvasScaleFactor() / proposed_scale_onscreen;
1976 double new_scale_ppm =
1977 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1978 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1979 }
1980
1981 if (m_bFollow) { // bounds-check the scale only if in autofollow
1982 proposed_scale_onscreen =
1983 wxMin(proposed_scale_onscreen,
1984 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1985 GetCanvasWidth()));
1986 proposed_scale_onscreen =
1987 wxMax(proposed_scale_onscreen,
1988 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1989 g_b_overzoom_x));
1990 }
1991
1992 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1993 }
1994
1995 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1996 m_singleChart->GetChartSkew() * PI / 180.,
1997 GetVPRotation());
1998 }
1999 } // new stack
2000
2001 else // No change in Chart Stack
2002 {
2003 if ((m_bFollow) && m_singleChart)
2004 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
2005 m_singleChart->GetChartSkew() * PI / 180.,
2006 GetVPRotation());
2007 }
2008
2009update_finish:
2010
2011 // TODO
2012 // if( bNewPiano ) UpdateControlBar();
2013
2014 m_bFirstAuto = false; // Auto open on program start
2015
2016 // If we need a Refresh(), do it here...
2017 // But don't duplicate a Refresh() done by SetViewPoint()
2018 if (bNewChart && !bNewView) Refresh(false);
2019
2020#ifdef ocpnUSE_GL
2021 // If a new chart, need to invalidate gl viewport for refresh
2022 // so the fbo gets flushed
2023 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
2024#endif
2025
2026 return bNewChart | bNewView;
2027}
2028
2029void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
2030 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
2031
2032 SetQuiltRefChart(db_index);
2033 if (ChartData) {
2034 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
2035 if (pc) {
2036 if (b_autoscale) {
2037 double best_scale_ppm = GetBestVPScale(pc);
2038 SetVPScale(best_scale_ppm);
2039 }
2040 } else
2041 SetQuiltRefChart(-1);
2042 } else
2043 SetQuiltRefChart(-1);
2044}
2045
2046void ChartCanvas::SelectQuiltRefChart(int selected_index) {
2047 std::vector<int> piano_chart_index_array =
2048 GetQuiltExtendedStackdbIndexArray();
2049 int current_db_index = piano_chart_index_array[selected_index];
2050
2051 SelectQuiltRefdbChart(current_db_index);
2052}
2053
2054double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
2055 if (pchart) {
2056 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
2057
2058 if ((g_bPreserveScaleOnX) ||
2059 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2060 double new_scale_ppm = GetVPScale();
2061 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2062 } else {
2063 // This logic will bring the new chart onscreen at roughly twice the true
2064 // paper scale equivalent.
2065 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2066 double equivalent_vp_scale =
2067 GetCanvasScaleFactor() / proposed_scale_onscreen;
2068 double new_scale_ppm =
2069 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2070 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2071 }
2072
2073 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2074 // set. Otherwise, we get severe performance problems on all platforms
2075
2076 double max_underzoom_multiplier = 2.0;
2077 if (GetVP().b_quilt) {
2078 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2079 pchart->GetChartType(),
2080 pchart->GetChartFamily());
2081 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2082 }
2083
2084 proposed_scale_onscreen = wxMin(
2085 proposed_scale_onscreen,
2086 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2087 max_underzoom_multiplier);
2088
2089 // And, do not allow excessive overzoom either
2090 proposed_scale_onscreen =
2091 wxMax(proposed_scale_onscreen,
2092 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2093
2094 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2095 } else
2096 return 1.0;
2097}
2098
2099void ChartCanvas::SetupCanvasQuiltMode(void) {
2100 if (GetQuiltMode()) // going to quilt mode
2101 {
2102 ChartData->LockCache();
2103
2104 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2105
2106 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2107
2108 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2109 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2110 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2111 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2112
2113 m_Piano->SetRoundedRectangles(true);
2114
2115 // Select the proper Ref chart
2116 int target_new_dbindex = -1;
2117 if (m_pCurrentStack) {
2118 target_new_dbindex =
2119 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2120
2121 if (-1 != target_new_dbindex) {
2122 if (!IsChartQuiltableRef(target_new_dbindex)) {
2123 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2124 int type = ChartData->GetDBChartType(target_new_dbindex);
2125
2126 // walk the stack up looking for a satisfactory chart
2127 int stack_index = m_pCurrentStack->CurrentStackEntry;
2128
2129 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2130 (stack_index >= 0)) {
2131 int proj_tent = ChartData->GetDBChartProj(
2132 m_pCurrentStack->GetDBIndex(stack_index));
2133 int type_tent = ChartData->GetDBChartType(
2134 m_pCurrentStack->GetDBIndex(stack_index));
2135
2136 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2137 if ((proj == proj_tent) && (type_tent == type)) {
2138 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2139 break;
2140 }
2141 }
2142 stack_index++;
2143 }
2144 }
2145 }
2146 }
2147
2148 if (IsChartQuiltableRef(target_new_dbindex))
2149 SelectQuiltRefdbChart(target_new_dbindex,
2150 false); // Try not to allow a scale change
2151 else
2152 SelectQuiltRefdbChart(-1, false);
2153
2154 m_singleChart = NULL; // Bye....
2155
2156 // Re-qualify the quilt reference chart selection
2157 AdjustQuiltRefChart();
2158
2159 // Restore projection type saved on last quilt mode toggle
2160 // TODO
2161 // if(g_sticky_projection != -1)
2162 // GetVP().SetProjectionType(g_sticky_projection);
2163 // else
2164 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2165 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2166
2167 } else // going to SC Mode
2168 {
2169 std::vector<int> empty_array;
2170 m_Piano->SetActiveKeyArray(empty_array);
2171 m_Piano->SetNoshowIndexArray(empty_array);
2172 m_Piano->SetEclipsedIndexArray(empty_array);
2173
2174 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2175 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2176 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2177 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2178 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2179
2180 m_Piano->SetRoundedRectangles(false);
2181 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2182 }
2183
2184 // When shifting from quilt to single chart mode, select the "best" single
2185 // chart to show
2186 if (!GetQuiltMode()) {
2187 if (ChartData && ChartData->IsValid()) {
2188 UnlockQuilt();
2189
2190 double tLat, tLon;
2191 if (m_bFollow == true) {
2192 tLat = gLat;
2193 tLon = gLon;
2194 } else {
2195 tLat = m_vLat;
2196 tLon = m_vLon;
2197 }
2198
2199 if (!m_singleChart) {
2200 // Build a temporary chart stack based on tLat, tLon
2201 ChartStack TempStack;
2202 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2203 m_groupIndex);
2204
2205 // Iterate over the quilt charts actually shown, looking for the
2206 // largest scale chart that will be in the new chartstack.... This
2207 // will (almost?) always be the reference chart....
2208
2209 ChartBase *Candidate_Chart = NULL;
2210 int cur_max_scale = (int)1e8;
2211
2212 ChartBase *pChart = GetFirstQuiltChart();
2213 while (pChart) {
2214 // Is this pChart in new stack?
2215 int tEntry =
2216 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2217 if (tEntry != -1) {
2218 if (pChart->GetNativeScale() < cur_max_scale) {
2219 Candidate_Chart = pChart;
2220 cur_max_scale = pChart->GetNativeScale();
2221 }
2222 }
2223 pChart = GetNextQuiltChart();
2224 }
2225
2226 m_singleChart = Candidate_Chart;
2227
2228 // If the quilt is empty, there is no "best" chart.
2229 // So, open the smallest scale chart in the current stack
2230 if (NULL == m_singleChart) {
2231 m_singleChart = ChartData->OpenStackChartConditional(
2232 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2233 CHART_FAMILY_DONTCARE);
2234 }
2235 }
2236
2237 // Invalidate all the charts in the quilt,
2238 // as any cached data may be region based and not have fullscreen coverage
2239 InvalidateAllQuiltPatchs();
2240
2241 if (m_singleChart) {
2242 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2243 std::vector<int> one_array;
2244 one_array.push_back(dbi);
2245 m_Piano->SetActiveKeyArray(one_array);
2246 }
2247
2248 if (m_singleChart) {
2249 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2250 }
2251 }
2252 // Invalidate the current stack so that it will be rebuilt on next tick
2253 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2254 }
2255}
2256
2257bool ChartCanvas::IsTempMenuBarEnabled() {
2258#ifdef __WXMSW__
2259 int major;
2260 wxGetOsVersion(&major);
2261 return (major >
2262 5); // For Windows, function is only available on Vista and above
2263#else
2264 return true;
2265#endif
2266}
2267
2268double ChartCanvas::GetCanvasRangeMeters() {
2269 int width, height;
2270 GetSize(&width, &height);
2271 int minDimension = wxMin(width, height);
2272
2273 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2274 range *= cos(GetVP().clat * PI / 180.);
2275 return range;
2276}
2277
2278void ChartCanvas::SetCanvasRangeMeters(double range) {
2279 int width, height;
2280 GetSize(&width, &height);
2281 int minDimension = wxMin(width, height);
2282
2283 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2284 SetVPScale(scale_ppm / 2);
2285}
2286
2287bool ChartCanvas::SetUserOwnship() {
2288 // Look for user defined ownship image
2289 // This may be found in the shared data location along with other user
2290 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2291 if (pWayPointMan && pWayPointMan->DoesIconExist(_T("ownship"))) {
2292 double factor_dusk = 0.5;
2293 double factor_night = 0.25;
2294
2295 wxBitmap *pbmp = pWayPointMan->GetIconBitmap(_T("ownship"));
2296 m_pos_image_user_day = new wxImage;
2297 *m_pos_image_user_day = pbmp->ConvertToImage();
2298 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2299
2300 int gimg_width = m_pos_image_user_day->GetWidth();
2301 int gimg_height = m_pos_image_user_day->GetHeight();
2302
2303 // Make dusk and night images
2304 m_pos_image_user_dusk = new wxImage;
2305 m_pos_image_user_night = new wxImage;
2306
2307 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2308 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2309
2310 for (int iy = 0; iy < gimg_height; iy++) {
2311 for (int ix = 0; ix < gimg_width; ix++) {
2312 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2313 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2314 m_pos_image_user_day->GetGreen(ix, iy),
2315 m_pos_image_user_day->GetBlue(ix, iy));
2316 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2317 hsv.value = hsv.value * factor_dusk;
2318 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2319 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2320 nrgb.blue);
2321
2322 hsv = wxImage::RGBtoHSV(rgb);
2323 hsv.value = hsv.value * factor_night;
2324 nrgb = wxImage::HSVtoRGB(hsv);
2325 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2326 nrgb.blue);
2327 }
2328 }
2329 }
2330
2331 // Make some alternate greyed out day/dusk/night images
2332 m_pos_image_user_grey_day = new wxImage;
2333 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2334
2335 m_pos_image_user_grey_dusk = new wxImage;
2336 m_pos_image_user_grey_night = new wxImage;
2337
2338 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2339 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2340
2341 for (int iy = 0; iy < gimg_height; iy++) {
2342 for (int ix = 0; ix < gimg_width; ix++) {
2343 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2344 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2345 m_pos_image_user_grey_day->GetGreen(ix, iy),
2346 m_pos_image_user_grey_day->GetBlue(ix, iy));
2347 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2348 hsv.value = hsv.value * factor_dusk;
2349 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2350 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2351 nrgb.blue);
2352
2353 hsv = wxImage::RGBtoHSV(rgb);
2354 hsv.value = hsv.value * factor_night;
2355 nrgb = wxImage::HSVtoRGB(hsv);
2356 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2357 nrgb.blue);
2358 }
2359 }
2360 }
2361
2362 // Make a yellow image for rendering under low accuracy chart conditions
2363 m_pos_image_user_yellow_day = new wxImage;
2364 m_pos_image_user_yellow_dusk = new wxImage;
2365 m_pos_image_user_yellow_night = new wxImage;
2366
2367 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2368 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2369 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2370
2371 for (int iy = 0; iy < gimg_height; iy++) {
2372 for (int ix = 0; ix < gimg_width; ix++) {
2373 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2374 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2375 m_pos_image_user_grey_day->GetGreen(ix, iy),
2376 m_pos_image_user_grey_day->GetBlue(ix, iy));
2377
2378 // Simply remove all "blue" from the greyscaled image...
2379 // so, what is not black becomes yellow.
2380 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2381 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2382 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2383
2384 hsv = wxImage::RGBtoHSV(rgb);
2385 hsv.value = hsv.value * factor_dusk;
2386 nrgb = wxImage::HSVtoRGB(hsv);
2387 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2388
2389 hsv = wxImage::RGBtoHSV(rgb);
2390 hsv.value = hsv.value * factor_night;
2391 nrgb = wxImage::HSVtoRGB(hsv);
2392 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2393 0);
2394 }
2395 }
2396 }
2397
2398 return true;
2399 } else
2400 return false;
2401}
2402
2404 m_display_size_mm = size;
2405
2406 // int sx, sy;
2407 // wxDisplaySize( &sx, &sy );
2408
2409 // Calculate logical pixels per mm for later reference.
2410 wxSize sd = g_Platform->getDisplaySize();
2411 double horizontal = sd.x;
2412 // Set DPI (Win) scale factor
2413 g_scaler = g_Platform->GetDisplayDIPMult(this);
2414
2415 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2416 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2417
2418 if (ps52plib) {
2419 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2420 ps52plib->SetPPMM(m_pix_per_mm);
2421 }
2422
2423 wxString msg;
2424 msg.Printf(
2425 _T("Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): ")
2426 _T("%d:%d "),
2427 m_display_size_mm, sd.x, sd.y);
2428 wxLogMessage(msg);
2429
2430 int ssx, ssy;
2431 ssx = g_monitor_info[g_current_monitor].width;
2432 ssy = g_monitor_info[g_current_monitor].height;
2433 msg.Printf(_T("monitor size: %d %d"), ssx, ssy);
2434 wxLogMessage(msg);
2435
2436 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2437}
2438#if 0
2439void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2440{
2441 wxString msg(event.m_string.c_str(), wxConvUTF8);
2442 // if cpus are removed between runs
2443 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2444 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2445 }
2446
2447 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2448 {
2449 compress_msg_array.RemoveAt(event.thread);
2450 compress_msg_array.Insert( msg, event.thread);
2451 }
2452 else
2453 compress_msg_array.Add(msg);
2454
2455
2456 wxString combined_msg;
2457 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2458 combined_msg += compress_msg_array[i];
2459 combined_msg += _T("\n");
2460 }
2461
2462 bool skip = false;
2463 pprog->Update(pprog_count, combined_msg, &skip );
2464 pprog->SetSize(pprog_size);
2465 if(skip)
2466 b_skipout = skip;
2467}
2468#endif
2469void ChartCanvas::InvalidateGL() {
2470 if (!m_glcc) return;
2471#ifdef ocpnUSE_GL
2472 if (g_bopengl) m_glcc->Invalidate();
2473#endif
2474 if (m_Compass) m_Compass->UpdateStatus(true);
2475}
2476
2477int ChartCanvas::GetCanvasChartNativeScale() {
2478 int ret = 1;
2479 if (!VPoint.b_quilt) {
2480 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2481 } else
2482 ret = (int)m_pQuilt->GetRefNativeScale();
2483
2484 return ret;
2485}
2486
2487ChartBase *ChartCanvas::GetChartAtCursor() {
2488 ChartBase *target_chart;
2489 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2490 target_chart = m_singleChart;
2491 else if (VPoint.b_quilt)
2492 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2493 else
2494 target_chart = NULL;
2495 return target_chart;
2496}
2497
2498ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2499 ChartBase *target_chart;
2500 if (VPoint.b_quilt)
2501 target_chart =
2502 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2503 else
2504 target_chart = NULL;
2505 return target_chart;
2506}
2507
2508int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2509 int new_dbIndex = -1;
2510 if (!VPoint.b_quilt) {
2511 if (m_pCurrentStack) {
2512 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2513 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2514 if (sc >= scale) {
2515 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2516 break;
2517 }
2518 }
2519 }
2520 } else {
2521 // Using the current quilt, select a useable reference chart
2522 // Said chart will be in the extended (possibly full-screen) stack,
2523 // And will have a scale equal to or just greater than the stipulated
2524 // value
2525 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2526 if (im > 0) {
2527 for (unsigned int is = 0; is < im; is++) {
2528 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2529 m_pQuilt->GetExtendedStackIndexArray()[is]);
2530 if ((m.Scale_ge(
2531 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2532 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2533 break;
2534 }
2535 }
2536 }
2537 }
2538
2539 return new_dbIndex;
2540}
2541
2542void ChartCanvas::EnablePaint(bool b_enable) {
2543 m_b_paint_enable = b_enable;
2544#ifdef ocpnUSE_GL
2545 if (m_glcc) m_glcc->EnablePaint(b_enable);
2546#endif
2547}
2548
2549bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2550
2551void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2552
2553std::vector<int> ChartCanvas::GetQuiltIndexArray(void) {
2554 return m_pQuilt->GetQuiltIndexArray();
2555 ;
2556}
2557
2558void ChartCanvas::SetQuiltMode(bool b_quilt) {
2559 VPoint.b_quilt = b_quilt;
2560 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2561}
2562
2563bool ChartCanvas::GetQuiltMode(void) { return VPoint.b_quilt; }
2564
2565int ChartCanvas::GetQuiltReferenceChartIndex(void) {
2566 return m_pQuilt->GetRefChartdbIndex();
2567}
2568
2569void ChartCanvas::InvalidateAllQuiltPatchs(void) {
2570 m_pQuilt->InvalidateAllQuiltPatchs();
2571}
2572
2573ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2574 return m_pQuilt->GetLargestScaleChart();
2575}
2576
2577ChartBase *ChartCanvas::GetFirstQuiltChart() {
2578 return m_pQuilt->GetFirstChart();
2579}
2580
2581ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2582
2583int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2584
2585void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2586 m_pQuilt->SetHiliteIndex(dbIndex);
2587}
2588
2589void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2590 m_pQuilt->SetHiliteIndexArray(hilite_array);
2591}
2592
2593void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2594 m_pQuilt->ClearHiliteIndexArray();
2595}
2596
2597std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2598 bool flag2) {
2599 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2600}
2601
2602int ChartCanvas::GetQuiltRefChartdbIndex(void) {
2603 return m_pQuilt->GetRefChartdbIndex();
2604}
2605
2606std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2607 return m_pQuilt->GetExtendedStackIndexArray();
2608}
2609
2610std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2611 return m_pQuilt->GetFullscreenIndexArray();
2612}
2613
2614std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2615 return m_pQuilt->GetEclipsedStackIndexArray();
2616}
2617
2618void ChartCanvas::InvalidateQuilt(void) { return m_pQuilt->Invalidate(); }
2619
2620double ChartCanvas::GetQuiltMaxErrorFactor() {
2621 return m_pQuilt->GetMaxErrorFactor();
2622}
2623
2624bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2625 return m_pQuilt->IsChartQuiltableRef(db_index);
2626}
2627
2628bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2629 double chartMaxScale =
2630 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2631 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2632}
2633
2634void ChartCanvas::StartMeasureRoute() {
2635 if (!m_routeState) { // no measure tool if currently creating route
2636 if (m_bMeasure_Active) {
2637 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2638 m_pMeasureRoute = NULL;
2639 }
2640
2641 m_bMeasure_Active = true;
2642 m_nMeasureState = 1;
2643 m_bDrawingRoute = false;
2644
2645 SetCursor(*pCursorPencil);
2646 Refresh();
2647 }
2648}
2649
2650void ChartCanvas::CancelMeasureRoute() {
2651 m_bMeasure_Active = false;
2652 m_nMeasureState = 0;
2653 m_bDrawingRoute = false;
2654
2655 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2656 m_pMeasureRoute = NULL;
2657
2658 SetCursor(*pCursorArrow);
2659}
2660
2661ViewPort &ChartCanvas::GetVP() { return VPoint; }
2662
2663void ChartCanvas::SetVP(ViewPort &vp) {
2664 VPoint = vp;
2665 VPoint.SetPixelScale(m_displayScale);
2666}
2667
2668// void ChartCanvas::SetFocus()
2669// {
2670// printf("set %d\n", m_canvasIndex);
2671// //wxWindow:SetFocus();
2672// }
2673
2674void ChartCanvas::TriggerDeferredFocus() {
2675 // #if defined(__WXGTK__) || defined(__WXOSX__)
2676
2677 m_deferredFocusTimer.Start(20, true);
2678
2679#if defined(__WXGTK__) || defined(__WXOSX__)
2680 gFrame->Raise();
2681#endif
2682
2683 // gFrame->Raise();
2684 // #else
2685 // SetFocus();
2686 // Refresh(true);
2687 // #endif
2688}
2689
2690void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2691 SetFocus();
2692 Refresh(true);
2693}
2694
2695void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2696 if (SendKeyEventToPlugins(event))
2697 return; // PlugIn did something, and does not want the canvas to do
2698 // anything else
2699
2700 int key_char = event.GetKeyCode();
2701 switch (key_char) {
2702 case '?':
2703 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2704 break;
2705 case '+':
2706 ZoomCanvas(g_plus_minus_zoom_factor, false);
2707 break;
2708 case '-':
2709 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2710 break;
2711 default:
2712 break;
2713 }
2714 if (g_benable_rotate) {
2715 switch (key_char) {
2716 case ']':
2717 RotateCanvas(1);
2718 Refresh();
2719 break;
2720
2721 case '[':
2722 RotateCanvas(-1);
2723 Refresh();
2724 break;
2725
2726 case '\\':
2727 DoRotateCanvas(0);
2728 break;
2729 }
2730 }
2731
2732 event.Skip();
2733}
2734
2735void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2736 if (SendKeyEventToPlugins(event))
2737 return; // PlugIn did something, and does not want the canvas to do
2738 // anything else
2739
2740 bool b_handled = false;
2741
2742 m_modkeys = event.GetModifiers();
2743
2744 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2745
2746#ifdef OCPN_ALT_MENUBAR
2747#ifndef __WXOSX__
2748 // If the permanent menubar is disabled, we show it temporarily when Alt is
2749 // pressed or when Alt + a letter is presssed (for the top-menu-level
2750 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2751 // some special cases.
2752 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2753 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2754 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2755 if (!g_bTempShowMenuBar) {
2756 g_bTempShowMenuBar = true;
2757 parent_frame->ApplyGlobalSettings(false);
2758 }
2759 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2760 event.Skip();
2761 return;
2762 }
2763 // If another key is pressed while Alt is down, do NOT toggle the menus when
2764 // Alt is released
2765 if (event.GetKeyCode() != WXK_ALT) {
2766 m_bMayToggleMenuBar = false;
2767 }
2768 }
2769#endif
2770#endif
2771
2772 // HOTKEYS
2773 switch (event.GetKeyCode()) {
2774 case WXK_TAB:
2775 // parent_frame->SwitchKBFocus( this );
2776 break;
2777
2778 case WXK_MENU:
2779 int x, y;
2780 event.GetPosition(&x, &y);
2781 m_FinishRouteOnKillFocus = false;
2782 CallPopupMenu(x, y);
2783 m_FinishRouteOnKillFocus = true;
2784 break;
2785
2786 case WXK_ALT:
2787 m_modkeys |= wxMOD_ALT;
2788 break;
2789
2790 case WXK_CONTROL:
2791 m_modkeys |= wxMOD_CONTROL;
2792 break;
2793
2794#ifdef __WXOSX__
2795 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2796 case WXK_RAW_CONTROL:
2797 m_modkeys |= wxMOD_RAW_CONTROL;
2798 break;
2799#endif
2800
2801 case WXK_LEFT:
2802 if (m_modkeys == wxMOD_CONTROL)
2803 parent_frame->DoStackDown(this);
2804 else if (g_bsmoothpanzoom) {
2805 StartTimedMovement();
2806 m_panx = -1;
2807 } else {
2808 PanCanvas(-panspeed, 0);
2809 }
2810 b_handled = true;
2811 break;
2812
2813 case WXK_UP:
2814 if (g_bsmoothpanzoom) {
2815 StartTimedMovement();
2816 m_pany = -1;
2817 } else
2818 PanCanvas(0, -panspeed);
2819 b_handled = true;
2820 break;
2821
2822 case WXK_RIGHT:
2823 if (m_modkeys == wxMOD_CONTROL)
2824 parent_frame->DoStackUp(this);
2825 else if (g_bsmoothpanzoom) {
2826 StartTimedMovement();
2827 m_panx = 1;
2828 } else
2829 PanCanvas(panspeed, 0);
2830 b_handled = true;
2831
2832 break;
2833
2834 case WXK_DOWN:
2835 if (g_bsmoothpanzoom) {
2836 StartTimedMovement();
2837 m_pany = 1;
2838 } else
2839 PanCanvas(0, panspeed);
2840 b_handled = true;
2841 break;
2842
2843 case WXK_F2:
2844 TogglebFollow();
2845 break;
2846
2847 case WXK_F3: {
2848 SetShowENCText(!GetShowENCText());
2849 Refresh(true);
2850 InvalidateGL();
2851 break;
2852 }
2853 case WXK_F4:
2854 if (!m_bMeasure_Active) {
2855 if (event.ShiftDown())
2856 m_bMeasure_DistCircle = true;
2857 else
2858 m_bMeasure_DistCircle = false;
2859
2860 StartMeasureRoute();
2861 } else {
2862 CancelMeasureRoute();
2863
2864 SetCursor(*pCursorArrow);
2865
2866 // SurfaceToolbar();
2867 InvalidateGL();
2868 Refresh(false);
2869 }
2870
2871 break;
2872
2873 case WXK_F5:
2874 parent_frame->ToggleColorScheme();
2875 gFrame->Raise();
2876 TriggerDeferredFocus();
2877 break;
2878
2879 case WXK_F6: {
2880 int mod = m_modkeys & wxMOD_SHIFT;
2881 if (mod != m_brightmod) {
2882 m_brightmod = mod;
2883 m_bbrightdir = !m_bbrightdir;
2884 }
2885
2886 if (!m_bbrightdir) {
2887 g_nbrightness -= 10;
2888 if (g_nbrightness <= MIN_BRIGHT) {
2889 g_nbrightness = MIN_BRIGHT;
2890 m_bbrightdir = true;
2891 }
2892 } else {
2893 g_nbrightness += 10;
2894 if (g_nbrightness >= MAX_BRIGHT) {
2895 g_nbrightness = MAX_BRIGHT;
2896 m_bbrightdir = false;
2897 }
2898 }
2899
2900 SetScreenBrightness(g_nbrightness);
2901 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2902
2903 SetFocus(); // just in case the external program steals it....
2904 gFrame->Raise(); // And reactivate the application main
2905
2906 break;
2907 }
2908
2909 case WXK_F7:
2910 parent_frame->DoStackDown(this);
2911 break;
2912
2913 case WXK_F8:
2914 parent_frame->DoStackUp(this);
2915 break;
2916
2917#ifndef __WXOSX__
2918 case WXK_F9: {
2919 ToggleCanvasQuiltMode();
2920 break;
2921 }
2922#endif
2923
2924 case WXK_F11:
2925 parent_frame->ToggleFullScreen();
2926 b_handled = true;
2927 break;
2928
2929 case WXK_F12: {
2930 if (m_modkeys == wxMOD_ALT) {
2931 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2932 } else {
2933 ToggleChartOutlines();
2934 }
2935 break;
2936 }
2937
2938 case WXK_PAUSE: // Drop MOB
2939 parent_frame->ActivateMOB();
2940 break;
2941
2942 // NUMERIC PAD
2943 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2944 case WXK_PAGEUP: {
2945 ZoomCanvas(g_plus_minus_zoom_factor, false);
2946 break;
2947 }
2948 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2949 case WXK_PAGEDOWN: {
2950 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2951 break;
2952 }
2953 case WXK_DELETE:
2954 case WXK_BACK:
2955 if (m_bMeasure_Active) {
2956 if (m_nMeasureState > 2) {
2957 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2958 m_pMeasureRoute->m_lastMousePointIndex =
2959 m_pMeasureRoute->GetnPoints();
2960 m_nMeasureState--;
2961 gFrame->RefreshAllCanvas();
2962 } else {
2963 CancelMeasureRoute();
2964 StartMeasureRoute();
2965 }
2966 }
2967 break;
2968 default:
2969 break;
2970 }
2971
2972 if (event.GetKeyCode() < 128) // ascii
2973 {
2974 int key_char = event.GetKeyCode();
2975
2976 // Handle both QWERTY and AZERTY keyboard separately for a few control
2977 // codes
2978 if (!g_b_assume_azerty) {
2979#ifdef __WXMAC__
2980 if (g_benable_rotate) {
2981 switch (key_char) {
2982 // On other platforms these are handled in OnKeyChar, which
2983 // (apparently) works better in some locales. On OS X it is better
2984 // to handle them here, since pressing Alt (which should change the
2985 // rotation speed) changes the key char and so prevents the keys
2986 // from working.
2987 case ']':
2988 RotateCanvas(1);
2989 b_handled = true;
2990 break;
2991
2992 case '[':
2993 RotateCanvas(-1);
2994 b_handled = true;
2995 break;
2996
2997 case '\\':
2998 DoRotateCanvas(0);
2999 b_handled = true;
3000 break;
3001 }
3002 }
3003#endif
3004 } else { // AZERTY
3005 switch (key_char) {
3006 case 43:
3007 ZoomCanvas(g_plus_minus_zoom_factor, false);
3008 break;
3009
3010 case 54: // '-' alpha/num pad
3011 // case 56: // '_' alpha/num pad
3012 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
3013 break;
3014 }
3015 }
3016
3017#ifdef __WXOSX__
3018 // Ctrl+Cmd+F toggles fullscreen on macOS
3019 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
3020 m_modkeys & wxMOD_RAW_CONTROL) {
3021 parent_frame->ToggleFullScreen();
3022 return;
3023 }
3024#endif
3025
3026 if (event.ControlDown()) key_char -= 64;
3027
3028 if (key_char >= '0' && key_char <= '9')
3029 SetGroupIndex(key_char - '0');
3030 else
3031
3032 switch (key_char) {
3033 case 'A':
3034 SetShowENCAnchor(!GetShowENCAnchor());
3035 ReloadVP();
3036
3037 break;
3038
3039 case 'C':
3040 parent_frame->ToggleColorScheme();
3041 break;
3042
3043 case 'D': {
3044 int x, y;
3045 event.GetPosition(&x, &y);
3046 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3047 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3048 // First find out what kind of chart is being used
3049 if (!pPopupDetailSlider) {
3050 if (VPoint.b_quilt) {
3051 if (m_pQuilt) {
3052 if (m_pQuilt->GetChartAtPix(
3053 VPoint,
3054 wxPoint(
3055 x, y))) // = null if no chart loaded for this point
3056 {
3057 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3058 ->GetChartType();
3059 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3060 ->GetChartFamily();
3061 }
3062 }
3063 } else {
3064 if (m_singleChart) {
3065 ChartType = m_singleChart->GetChartType();
3066 ChartFam = m_singleChart->GetChartFamily();
3067 }
3068 }
3069 // If a charttype is found show the popupslider
3070 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3071 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3072 pPopupDetailSlider = new PopUpDSlide(
3073 this, -1, ChartType, ChartFam,
3074 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3075 wxDefaultSize, wxSIMPLE_BORDER, _T(""));
3076 if (pPopupDetailSlider) pPopupDetailSlider->Show();
3077 }
3078 } else //( !pPopupDetailSlider ) close popupslider
3079 {
3080 if (pPopupDetailSlider) pPopupDetailSlider->Close();
3081 pPopupDetailSlider = NULL;
3082 }
3083 break;
3084 }
3085
3086 case 'E':
3087 m_nmea_log->Show();
3088 m_nmea_log->Raise();
3089 break;
3090
3091 case 'L':
3092 SetShowENCLights(!GetShowENCLights());
3093 ReloadVP();
3094
3095 break;
3096
3097 case 'M':
3098 if (event.ShiftDown())
3099 m_bMeasure_DistCircle = true;
3100 else
3101 m_bMeasure_DistCircle = false;
3102
3103 StartMeasureRoute();
3104 break;
3105
3106 case 'N':
3107 if (g_bInlandEcdis && ps52plib) {
3108 SetENCDisplayCategory((_DisCat)STANDARD);
3109 }
3110 break;
3111
3112 case 'O':
3113 ToggleChartOutlines();
3114 break;
3115
3116 case 'Q':
3117 ToggleCanvasQuiltMode();
3118 break;
3119
3120 case 'P':
3121 parent_frame->ToggleTestPause();
3122 break;
3123 case 'R':
3124 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3125 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3126 g_iNavAidRadarRingsNumberVisible = 1;
3127 else if (!g_bNavAidRadarRingsShown &&
3128 g_iNavAidRadarRingsNumberVisible == 1)
3129 g_iNavAidRadarRingsNumberVisible = 0;
3130 break;
3131 case 'S':
3132 SetShowENCDepth(!m_encShowDepth);
3133 ReloadVP();
3134 break;
3135
3136 case 'T':
3137 SetShowENCText(!GetShowENCText());
3138 ReloadVP();
3139 break;
3140
3141 case 'U':
3142 SetShowENCDataQual(!GetShowENCDataQual());
3143 ReloadVP();
3144 break;
3145
3146 case 'V':
3147 m_bShowNavobjects = !m_bShowNavobjects;
3148 Refresh(true);
3149 break;
3150
3151 case 'W': // W Toggle CPA alarm
3152 ToggleCPAWarn();
3153
3154 break;
3155
3156 case 1: // Ctrl A
3157 TogglebFollow();
3158
3159 break;
3160
3161 case 2: // Ctrl B
3162 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3163 break;
3164
3165 case 13: // Ctrl M // Drop Marker at cursor
3166 {
3167 if (event.ControlDown()) gFrame->DropMarker(false);
3168 break;
3169 }
3170
3171 case 14: // Ctrl N - Activate next waypoint in a route
3172 {
3173 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3174 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3175 if ((indexActive + 1) <= r->GetnPoints()) {
3176 g_pRouteMan->ActivateNextPoint(r, true);
3177 InvalidateGL();
3178 Refresh(false);
3179 }
3180 }
3181 break;
3182 }
3183
3184 case 15: // Ctrl O - Drop Marker at boat's position
3185 {
3186 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3187 break;
3188 }
3189
3190 case 32: // Special needs use space bar
3191 {
3192 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3193 break;
3194 }
3195
3196 case -32: // Ctrl Space // Drop MOB
3197 {
3198 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3199
3200 break;
3201 }
3202
3203 case -20: // Ctrl ,
3204 {
3205 parent_frame->DoSettings();
3206 break;
3207 }
3208 case 17: // Ctrl Q
3209 parent_frame->Close();
3210 return;
3211
3212 case 18: // Ctrl R
3213 StartRoute();
3214 return;
3215
3216 case 20: // Ctrl T
3217 if (NULL == pGoToPositionDialog) // There is one global instance of
3218 // the Go To Position Dialog
3219 pGoToPositionDialog = new GoToPositionDialog(this);
3220 pGoToPositionDialog->SetCanvas(this);
3221 pGoToPositionDialog->Show();
3222 break;
3223
3224 case 25: // Ctrl Y
3225 if (undo->AnythingToRedo()) {
3226 undo->RedoNextAction();
3227 InvalidateGL();
3228 Refresh(false);
3229 }
3230 break;
3231
3232 case 26:
3233 if (event.ShiftDown()) { // Shift-Ctrl-Z
3234 if (undo->AnythingToRedo()) {
3235 undo->RedoNextAction();
3236 InvalidateGL();
3237 Refresh(false);
3238 }
3239 } else { // Ctrl Z
3240 if (undo->AnythingToUndo()) {
3241 undo->UndoLastAction();
3242 InvalidateGL();
3243 Refresh(false);
3244 }
3245 }
3246 break;
3247
3248 case 27:
3249 // Generic break
3250 if (m_bMeasure_Active) {
3251 CancelMeasureRoute();
3252
3253 SetCursor(*pCursorArrow);
3254
3255 // SurfaceToolbar();
3256 gFrame->RefreshAllCanvas();
3257 }
3258
3259 if (m_routeState) // creating route?
3260 {
3261 FinishRoute();
3262 // SurfaceToolbar();
3263 InvalidateGL();
3264 Refresh(false);
3265 }
3266
3267 break;
3268
3269 case 7: // Ctrl G
3270 switch (gamma_state) {
3271 case (0):
3272 r_gamma_mult = 0;
3273 g_gamma_mult = 1;
3274 b_gamma_mult = 0;
3275 gamma_state = 1;
3276 break;
3277 case (1):
3278 r_gamma_mult = 1;
3279 g_gamma_mult = 0;
3280 b_gamma_mult = 0;
3281 gamma_state = 2;
3282 break;
3283 case (2):
3284 r_gamma_mult = 1;
3285 g_gamma_mult = 1;
3286 b_gamma_mult = 1;
3287 gamma_state = 0;
3288 break;
3289 }
3290 SetScreenBrightness(g_nbrightness);
3291
3292 break;
3293
3294 case 9: // Ctrl I
3295 if (event.ControlDown()) {
3296 m_bShowCompassWin = !m_bShowCompassWin;
3297 SetShowGPSCompassWindow(m_bShowCompassWin);
3298 Refresh(false);
3299 }
3300 break;
3301
3302 default:
3303 break;
3304
3305 } // switch
3306 }
3307
3308 // Allow OnKeyChar to catch the key events too.
3309 if (!b_handled) {
3310 event.Skip();
3311 }
3312}
3313
3314void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3315 if (SendKeyEventToPlugins(event))
3316 return; // PlugIn did something, and does not want the canvas to do
3317 // anything else
3318
3319 switch (event.GetKeyCode()) {
3320 case WXK_TAB:
3321 parent_frame->SwitchKBFocus(this);
3322 break;
3323
3324 case WXK_LEFT:
3325 case WXK_RIGHT:
3326 m_panx = 0;
3327 if (!m_pany) m_panspeed = 0;
3328 break;
3329
3330 case WXK_UP:
3331 case WXK_DOWN:
3332 m_pany = 0;
3333 if (!m_panx) m_panspeed = 0;
3334 break;
3335
3336 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3337 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3338 case WXK_PAGEUP:
3339 case WXK_PAGEDOWN:
3340 if (m_mustmove) DoMovement(m_mustmove);
3341
3342 m_zoom_factor = 1;
3343 break;
3344
3345 case WXK_ALT:
3346 m_modkeys &= ~wxMOD_ALT;
3347#ifdef OCPN_ALT_MENUBAR
3348#ifndef __WXOSX__
3349 // If the permanent menu bar is disabled, and we are not in the middle of
3350 // another key combo, then show the menu bar temporarily when Alt is
3351 // released (or hide it if already visible).
3352 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3353 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3354 parent_frame->ApplyGlobalSettings(false);
3355 }
3356 m_bMayToggleMenuBar = true;
3357#endif
3358#endif
3359 break;
3360
3361 case WXK_CONTROL:
3362 m_modkeys &= ~wxMOD_CONTROL;
3363 break;
3364 }
3365
3366 if (event.GetKeyCode() < 128) // ascii
3367 {
3368 int key_char = event.GetKeyCode();
3369
3370 // Handle both QWERTY and AZERTY keyboard separately for a few control
3371 // codes
3372 if (!g_b_assume_azerty) {
3373 switch (key_char) {
3374 case '+':
3375 case '=':
3376 case '-':
3377 case '_':
3378 case 54:
3379 case 56: // '_' alpha/num pad
3380 DoMovement(m_mustmove);
3381
3382 // m_zoom_factor = 1;
3383 break;
3384 case '[':
3385 case ']':
3386 DoMovement(m_mustmove);
3387 m_rotation_speed = 0;
3388 break;
3389 }
3390 } else {
3391 switch (key_char) {
3392 case 43:
3393 case 54: // '-' alpha/num pad
3394 case 56: // '_' alpha/num pad
3395 DoMovement(m_mustmove);
3396
3397 m_zoom_factor = 1;
3398 break;
3399 }
3400 }
3401 }
3402 event.Skip();
3403}
3404
3405void ChartCanvas::ToggleChartOutlines(void) {
3406 m_bShowOutlines = !m_bShowOutlines;
3407
3408 Refresh(false);
3409
3410#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3411 // needs a full refresh
3412 if (g_bopengl) InvalidateGL();
3413#endif
3414}
3415
3416void ChartCanvas::ToggleLookahead() {
3417 m_bLookAhead = !m_bLookAhead;
3418 m_OSoffsetx = 0; // center ownship
3419 m_OSoffsety = 0;
3420}
3421
3422void ChartCanvas::SetUpMode(int mode) {
3423 m_upMode = mode;
3424
3425 if (mode != NORTH_UP_MODE) {
3426 // Stuff the COGAvg table in case COGUp is selected
3427 double stuff = 0;
3428 if (!std::isnan(gCog)) stuff = gCog;
3429
3430 if (g_COGAvgSec > 0) {
3431 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3432 }
3433 g_COGAvg = stuff;
3434 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3435 } else {
3436 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3437 SetVPRotation(GetVPSkew());
3438 else
3439 SetVPRotation(0); /* reset to north up */
3440 }
3441
3442 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3443 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3444
3445 UpdateGPSCompassStatusBox(true);
3446 gFrame->DoChartUpdate();
3447}
3448
3449bool ChartCanvas::DoCanvasCOGSet(void) {
3450 if (GetUpMode() == NORTH_UP_MODE) return false;
3451 double cog_use = g_COGAvg;
3452 if (g_btenhertz) cog_use = gCog;
3453
3454 double rotation = 0;
3455 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3456 rotation = -gHdt * PI / 180.;
3457 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3458 rotation = -cog_use * PI / 180.;
3459
3460 SetVPRotation(rotation);
3461 return true;
3462}
3463
3464double easeOutCubic(double t) {
3465 // Starts quickly and slows down toward the end
3466 return 1.0 - pow(1.0 - t, 3.0);
3467}
3468
3469void ChartCanvas::StartChartDragInertia() {
3470 //
3471 // printf("\nStart ChartDragInertia\n");
3472 m_bChartDragging = false;
3473
3474 // Set some parameters
3475 m_chart_drag_inertia_time = 750; // msec
3476 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3477 m_last_elapsed = 0;
3478
3479 // Calculate ending drag velocity
3480 size_t n_vel = 10;
3481 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3482 int xacc = 0;
3483 int yacc = 0;
3484 double tacc = 0;
3485 size_t length = m_drag_vec_t.size();
3486 for (size_t i = 0; i < n_vel; i++) {
3487 xacc += m_drag_vec_x.at(length - 1 - i);
3488 yacc += m_drag_vec_y.at(length - 1 - i);
3489 tacc += m_drag_vec_t.at(length - 1 - i);
3490 // printf("%d %g\n", xacc, tacc);
3491 }
3492 m_chart_drag_velocity_x = xacc / tacc;
3493 m_chart_drag_velocity_y = yacc / tacc;
3494
3495 m_chart_drag_inertia_active = true;
3496
3497 // First callback as fast as possible.
3498 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3499
3500 // printf(" Drag parms %d %d %g\n", m_chart_drag_total_x,
3501 // m_chart_drag_total_y, m_chart_drag_total_time);
3502}
3503
3504void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3505 if (!m_chart_drag_inertia_active) return;
3506
3507 // Calculate time fraction from 0..1
3508 wxLongLong now = wxGetLocalTimeMillis();
3509 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3510 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3511 if (t > 1.0) t = 1.0;
3512 double e = 1.0 - easeOutCubic(t); // 0..1
3513
3514 double dx =
3515 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3516 double dy =
3517 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3518
3519 // double distance = pow((pow(dx, 2) + pow(dy, 2)), 0.5);
3520 // printf(" %5g %5g %5g %5g pix/sec\n", elapsed,
3521 // elapsed - m_last_elapsed, distance, distance * 1000 / elapsed);
3522
3523 m_last_elapsed = elapsed;
3524
3525 // Ensure that target destination lies on whole-pixel boundary
3526 // This allows the render engine to use a faster FBO copy method for drawing
3527 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3528 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3529 double inertia_lat, inertia_lon;
3530 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3531 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3532
3533 Refresh(false);
3534
3535 // Stop condition
3536 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3537 m_chart_drag_inertia_timer.Stop();
3538 m_chart_drag_inertia_active = false;
3539
3540 // Disable chart pan movement logic
3541 m_target_lat = GetVP().clat;
3542 m_target_lon = GetVP().clon;
3543 m_pan_drag.x = m_pan_drag.y = 0;
3544 m_panx = m_pany = 0;
3545
3546 } else {
3547 int target_redraw_interval = 40; // msec
3548 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3549 }
3550}
3551
3552void ChartCanvas::StopMovement() {
3553 m_panx = m_pany = 0;
3554 m_panspeed = 0;
3555 m_zoom_factor = 1;
3556 m_rotation_speed = 0;
3557 m_mustmove = 0;
3558#if 0
3559#if !defined(__WXGTK__) && !defined(__WXQT__)
3560 SetFocus();
3561 gFrame->Raise();
3562#endif
3563#endif
3564}
3565
3566/* instead of integrating in timer callbacks
3567 (which do not always get called fast enough)
3568 we can perform the integration of movement
3569 at each render frame based on the time change */
3570bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3571 // Start/restart the stop movement timer
3572 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3573
3574 if (!pMovementTimer->IsRunning()) {
3575 // printf("timer not running, starting\n");
3576 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3577 }
3578
3579 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3580 // already moving, gets called again because of key-repeat event
3581 return false;
3582 }
3583
3584 m_last_movement_time = wxDateTime::UNow();
3585
3586 return true;
3587}
3588void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3589 int nstep) {
3590 // Save the target
3591 m_target_lat = target_lat;
3592 m_target_lon = target_lon;
3593
3594 // Save the start point
3595 m_start_lat = GetVP().clat;
3596 m_start_lon = GetVP().clon;
3597
3598 m_VPMovementTimer.Start(1, true); // oneshot
3599 m_timed_move_vp_active = true;
3600 m_stvpc = 0;
3601 m_timedVP_step = nstep;
3602}
3603
3604void ChartCanvas::DoTimedMovementVP() {
3605 if (!m_timed_move_vp_active) return; // not active
3606 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3607 StopMovement();
3608 return;
3609 }
3610 // Stop condition
3611 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3612 double d2 =
3613 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3614 d2 = pow(d2, 0.5);
3615
3616 if (d2 < one_pix) {
3617 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3618 StopMovementVP();
3619 return;
3620 }
3621
3622 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3623 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3624 // StopMovementVP();
3625 // return;
3626 // }
3627
3628 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3629 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3630
3631 m_run_lat = new_lat;
3632 m_run_lon = new_lon;
3633
3634 // printf(" Timed\n");
3635 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3636}
3637
3638void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3639
3640void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3641
3642void ChartCanvas::StartTimedMovementTarget() {}
3643
3644void ChartCanvas::DoTimedMovementTarget() {}
3645
3646void ChartCanvas::StopMovementTarget() {}
3647
3648void ChartCanvas::DoTimedMovement() {
3649 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3650 !m_rotation_speed)
3651 return; /* not moving */
3652
3653 wxDateTime now = wxDateTime::UNow();
3654 long dt = 0;
3655 if (m_last_movement_time.IsValid())
3656 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3657
3658 m_last_movement_time = now;
3659
3660 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3661 dt = 500;
3662
3663 DoMovement(dt);
3664}
3665
3667 /* if we get here quickly assume 1ms so that some movement occurs */
3668 if (dt == 0) dt = 1;
3669
3670 m_mustmove -= dt;
3671 if (m_mustmove < 0) m_mustmove = 0;
3672
3673 if (m_pan_drag.x || m_pan_drag.y) {
3674 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3675 m_pan_drag.x = m_pan_drag.y = 0;
3676 }
3677
3678 if (m_panx || m_pany) {
3679 const double slowpan = .1, maxpan = 2;
3680 if (m_modkeys == wxMOD_ALT)
3681 m_panspeed = slowpan;
3682 else {
3683 m_panspeed += (double)dt / 500; /* apply acceleration */
3684 m_panspeed = wxMin(maxpan, m_panspeed);
3685 }
3686 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3687 }
3688
3689 if (m_zoom_factor != 1) {
3690 double alpha = 400, beta = 1.5;
3691 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3692
3693 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3694
3695 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3696
3697 // Try to hit the zoom target exactly.
3698 // if(m_wheelzoom_stop_oneshot > 0)
3699 {
3700 if (zoom_factor > 1) {
3701 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3702 zoom_factor = VPoint.chart_scale / m_zoom_target;
3703 }
3704
3705 else if (zoom_factor < 1) {
3706 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3707 zoom_factor = VPoint.chart_scale / m_zoom_target;
3708 }
3709 }
3710
3711 if (fabs(zoom_factor - 1) > 1e-4) {
3712 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3713 } else {
3714 StopMovement();
3715 }
3716
3717 if (m_wheelzoom_stop_oneshot > 0) {
3718 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3719 m_wheelzoom_stop_oneshot = 0;
3720 StopMovement();
3721 }
3722
3723 // Don't overshoot the zoom target.
3724 if (zoom_factor > 1) {
3725 if (VPoint.chart_scale <= m_zoom_target) {
3726 m_wheelzoom_stop_oneshot = 0;
3727 StopMovement();
3728 }
3729 } else if (zoom_factor < 1) {
3730 if (VPoint.chart_scale >= m_zoom_target) {
3731 m_wheelzoom_stop_oneshot = 0;
3732 StopMovement();
3733 }
3734 }
3735 }
3736 }
3737
3738 if (m_rotation_speed) { /* in degrees per second */
3739 double speed = m_rotation_speed;
3740 if (m_modkeys == wxMOD_ALT) speed /= 10;
3741 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3742 }
3743}
3744
3745void ChartCanvas::SetColorScheme(ColorScheme cs) {
3746 SetAlertString(_T(""));
3747
3748 // Setup ownship image pointers
3749 switch (cs) {
3750 case GLOBAL_COLOR_SCHEME_DAY:
3751 m_pos_image_red = &m_os_image_red_day;
3752 m_pos_image_grey = &m_os_image_grey_day;
3753 m_pos_image_yellow = &m_os_image_yellow_day;
3754 m_pos_image_user = m_pos_image_user_day;
3755 m_pos_image_user_grey = m_pos_image_user_grey_day;
3756 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3757 m_cTideBitmap = m_bmTideDay;
3758 m_cCurrentBitmap = m_bmCurrentDay;
3759
3760 break;
3761 case GLOBAL_COLOR_SCHEME_DUSK:
3762 m_pos_image_red = &m_os_image_red_dusk;
3763 m_pos_image_grey = &m_os_image_grey_dusk;
3764 m_pos_image_yellow = &m_os_image_yellow_dusk;
3765 m_pos_image_user = m_pos_image_user_dusk;
3766 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3767 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3768 m_cTideBitmap = m_bmTideDusk;
3769 m_cCurrentBitmap = m_bmCurrentDusk;
3770 break;
3771 case GLOBAL_COLOR_SCHEME_NIGHT:
3772 m_pos_image_red = &m_os_image_red_night;
3773 m_pos_image_grey = &m_os_image_grey_night;
3774 m_pos_image_yellow = &m_os_image_yellow_night;
3775 m_pos_image_user = m_pos_image_user_night;
3776 m_pos_image_user_grey = m_pos_image_user_grey_night;
3777 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3778 m_cTideBitmap = m_bmTideNight;
3779 m_cCurrentBitmap = m_bmCurrentNight;
3780 break;
3781 default:
3782 m_pos_image_red = &m_os_image_red_day;
3783 m_pos_image_grey = &m_os_image_grey_day;
3784 m_pos_image_yellow = &m_os_image_yellow_day;
3785 m_pos_image_user = m_pos_image_user_day;
3786 m_pos_image_user_grey = m_pos_image_user_grey_day;
3787 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3788 m_cTideBitmap = m_bmTideDay;
3789 m_cCurrentBitmap = m_bmCurrentDay;
3790 break;
3791 }
3792
3793 CreateDepthUnitEmbossMaps(cs);
3794 CreateOZEmbossMapData(cs);
3795
3796 // Set up fog effect base color
3797 m_fog_color = wxColor(
3798 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3799 float dim = 1.0;
3800 switch (cs) {
3801 case GLOBAL_COLOR_SCHEME_DUSK:
3802 dim = 0.5;
3803 break;
3804 case GLOBAL_COLOR_SCHEME_NIGHT:
3805 dim = 0.25;
3806 break;
3807 default:
3808 break;
3809 }
3810 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3811 m_fog_color.Blue() * dim);
3812
3813 // Really dark
3814#if 0
3815 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3816 SetBackgroundColour( wxColour(0,0,0) );
3817
3818 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3819 }
3820 else{
3821 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3822#ifndef __WXMAC__
3823 SetBackgroundColour( wxNullColour );
3824#endif
3825 }
3826#endif
3827
3828 // UpdateToolbarColorScheme(cs);
3829
3830 m_Piano->SetColorScheme(cs);
3831
3832 m_Compass->SetColorScheme(cs);
3833
3834 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3835
3836 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3837
3838 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3839 if (m_notification_button) {
3840 m_notification_button->SetColorScheme(cs);
3841 }
3842
3843#ifdef ocpnUSE_GL
3844 if (g_bopengl && m_glcc) {
3845 m_glcc->SetColorScheme(cs);
3846 g_glTextureManager->ClearAllRasterTextures();
3847 // m_glcc->FlushFBO();
3848 }
3849#endif
3850 SetbTCUpdate(true); // force re-render of tide/current locators
3851 m_brepaint_piano = true;
3852
3853 ReloadVP();
3854
3855 m_cs = cs;
3856}
3857
3858wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3859 wxImage img = Bitmap.ConvertToImage();
3860 int sx = img.GetWidth();
3861 int sy = img.GetHeight();
3862
3863 wxImage new_img(img);
3864
3865 for (int i = 0; i < sx; i++) {
3866 for (int j = 0; j < sy; j++) {
3867 if (!img.IsTransparent(i, j)) {
3868 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3869 (unsigned char)(img.GetGreen(i, j) * factor),
3870 (unsigned char)(img.GetBlue(i, j) * factor));
3871 }
3872 }
3873 }
3874
3875 wxBitmap ret = wxBitmap(new_img);
3876
3877 return ret;
3878}
3879
3880void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3881 int max) {
3882 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3883 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3884
3885 if (!m_pBrightPopup) {
3886 // Calculate size
3887 int x, y;
3888 GetTextExtent(_T("MAX"), &x, &y, NULL, NULL, pfont);
3889
3890 m_pBrightPopup = new TimedPopupWin(this, 3);
3891
3892 m_pBrightPopup->SetSize(x, y);
3893 m_pBrightPopup->Move(120, 120);
3894 }
3895
3896 int bmpsx = m_pBrightPopup->GetSize().x;
3897 int bmpsy = m_pBrightPopup->GetSize().y;
3898
3899 wxBitmap bmp(bmpsx, bmpsx);
3900 wxMemoryDC mdc(bmp);
3901
3902 mdc.SetTextForeground(GetGlobalColor(_T("GREEN4")));
3903 mdc.SetBackground(wxBrush(GetGlobalColor(_T("UINFD"))));
3904 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3905 mdc.SetBrush(wxBrush(GetGlobalColor(_T("UINFD"))));
3906 mdc.Clear();
3907
3908 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3909
3910 mdc.SetFont(*pfont);
3911 wxString val;
3912
3913 if (brightness == max)
3914 val = _T("MAX");
3915 else if (brightness == min)
3916 val = _T("MIN");
3917 else
3918 val.Printf(_T("%3d"), brightness);
3919
3920 mdc.DrawText(val, 0, 0);
3921
3922 mdc.SelectObject(wxNullBitmap);
3923
3924 m_pBrightPopup->SetBitmap(bmp);
3925 m_pBrightPopup->Show();
3926 m_pBrightPopup->Refresh();
3927}
3928
3929void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3930 m_b_rot_hidef = true;
3931 ReloadVP();
3932}
3933
3934void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3935 if (!g_bRollover) return;
3936
3937 bool b_need_refresh = false;
3938
3939 wxSize win_size = GetSize() * m_displayScale;
3940 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3941
3942 // Handle the AIS Rollover Window first
3943 bool showAISRollover = false;
3944 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3945 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3946 SelectItem *pFind = pSelectAIS->FindSelection(
3947 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3948 if (pFind) {
3949 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3950 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3951
3952 if (ptarget) {
3953 showAISRollover = true;
3954
3955 if (NULL == m_pAISRolloverWin) {
3956 m_pAISRolloverWin = new RolloverWin(this);
3957 m_pAISRolloverWin->IsActive(false);
3958 b_need_refresh = true;
3959 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3960 m_AISRollover_MMSI != FoundAIS_MMSI) {
3961 // Sometimes the mouse moves fast enough to get over a new AIS
3962 // target before the one-shot has fired to remove the old target.
3963 // Result: wrong target data is shown.
3964 // Detect this case,close the existing rollover ASAP, and restart
3965 // the timer.
3966 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3967 m_pAISRolloverWin->IsActive(false);
3968 m_AISRollover_MMSI = 0;
3969 Refresh();
3970 return;
3971 }
3972
3973 m_AISRollover_MMSI = FoundAIS_MMSI;
3974
3975 if (!m_pAISRolloverWin->IsActive()) {
3976 wxString s = ptarget->GetRolloverString();
3977 m_pAISRolloverWin->SetString(s);
3978
3979 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3980 AIS_ROLLOVER, win_size);
3981 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3982 m_pAISRolloverWin->IsActive(true);
3983 b_need_refresh = true;
3984 }
3985 }
3986 } else {
3987 m_AISRollover_MMSI = 0;
3988 showAISRollover = false;
3989 }
3990 }
3991
3992 // Maybe turn the rollover off
3993 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3994 m_pAISRolloverWin->IsActive(false);
3995 m_AISRollover_MMSI = 0;
3996 b_need_refresh = true;
3997 }
3998
3999 // Now the Route info rollover
4000 // Show the route segment info
4001 bool showRouteRollover = false;
4002
4003 if (NULL == m_pRolloverRouteSeg) {
4004 // Get a list of all selectable sgements, and search for the first
4005 // visible segment as the rollover target.
4006
4007 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4008 SelectableItemList SelList = pSelect->FindSelectionList(
4009 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
4010 wxSelectableItemListNode *node = SelList.GetFirst();
4011 while (node) {
4012 SelectItem *pFindSel = node->GetData();
4013
4014 Route *pr = (Route *)pFindSel->m_pData3; // candidate
4015
4016 if (pr && pr->IsVisible()) {
4017 m_pRolloverRouteSeg = pFindSel;
4018 showRouteRollover = true;
4019
4020 if (NULL == m_pRouteRolloverWin) {
4021 m_pRouteRolloverWin = new RolloverWin(this, 10);
4022 m_pRouteRolloverWin->IsActive(false);
4023 }
4024
4025 if (!m_pRouteRolloverWin->IsActive()) {
4026 wxString s;
4027 RoutePoint *segShow_point_a =
4028 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4029 RoutePoint *segShow_point_b =
4030 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4031
4032 double brg, dist;
4033 DistanceBearingMercator(
4034 segShow_point_b->m_lat, segShow_point_b->m_lon,
4035 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4036
4037 if (!pr->m_bIsInLayer)
4038 s.Append(_("Route") + _T(": "));
4039 else
4040 s.Append(_("Layer Route: "));
4041
4042 if (pr->m_RouteNameString.IsEmpty())
4043 s.Append(_("(unnamed)"));
4044 else
4045 s.Append(pr->m_RouteNameString);
4046
4047 s << _T("\n") << _("Total Length: ")
4048 << FormatDistanceAdaptive(pr->m_route_length) << _T("\n")
4049 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4050 << segShow_point_b->GetName() << _T("\n");
4051
4052 if (g_bShowTrue)
4053 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4054 (int)floor(brg + 0.5), 0x00B0);
4055 if (g_bShowMag) {
4056 double latAverage =
4057 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4058 double lonAverage =
4059 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4060 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4061
4062 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4063 (int)floor(varBrg + 0.5), 0x00B0);
4064 }
4065
4066 s << FormatDistanceAdaptive(dist);
4067
4068 // Compute and display cumulative distance from route start point to
4069 // current leg end point and RNG,TTG,ETA from ship to current leg end
4070 // point for active route
4071 double shiptoEndLeg = 0.;
4072 bool validActive = false;
4073 if (pr->IsActive() &&
4074 pr->pRoutePointList->GetFirst()->GetData()->m_bIsActive)
4075 validActive = true;
4076
4077 if (segShow_point_a != pr->pRoutePointList->GetFirst()->GetData()) {
4078 wxRoutePointListNode *node =
4079 (pr->pRoutePointList)->GetFirst()->GetNext();
4080 RoutePoint *prp;
4081 float dist_to_endleg = 0;
4082 wxString t;
4083
4084 while (node) {
4085 prp = node->GetData();
4086 if (validActive)
4087 shiptoEndLeg += prp->m_seg_len;
4088 else if (prp->m_bIsActive)
4089 validActive = true;
4090 dist_to_endleg += prp->m_seg_len;
4091 if (prp->IsSame(segShow_point_a)) break;
4092 node = node->GetNext();
4093 }
4094 s << _T(" (+") << FormatDistanceAdaptive(dist_to_endleg) << _T(")");
4095 }
4096 // write from ship to end selected leg point data if the route is
4097 // active
4098 if (validActive) {
4099 s << _T("\n") << _("From Ship To") << _T(" ")
4100 << segShow_point_b->GetName() << _T("\n");
4101 shiptoEndLeg +=
4102 g_pRouteMan
4103 ->GetCurrentRngToActivePoint(); // add distance from ship
4104 // to active point
4105 shiptoEndLeg +=
4106 segShow_point_b
4107 ->m_seg_len; // add the lenght of the selected leg
4108 s << FormatDistanceAdaptive(shiptoEndLeg);
4109 // ensure sog/cog are valid and vmg is positive to keep data
4110 // coherent
4111 double vmg = 0.;
4112 if (!std::isnan(gCog) && !std::isnan(gSog))
4113 vmg = gSog *
4114 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4115 PI / 180.);
4116 if (vmg > 0.) {
4117 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4118 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4119 s << _T(" - ")
4120 << wxString(ttg_sec > SECONDS_PER_DAY
4121 ? ttg_span.Format(_("%Dd %H:%M"))
4122 : ttg_span.Format(_("%H:%M")));
4123 wxDateTime dtnow, eta;
4124 eta = dtnow.SetToCurrent().Add(ttg_span);
4125 s << _T(" - ") << eta.Format(_T("%b")).Mid(0, 4)
4126 << eta.Format(_T(" %d %H:%M"));
4127 } else
4128 s << _T(" ---- ----");
4129 }
4130 m_pRouteRolloverWin->SetString(s);
4131
4132 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4133 LEG_ROLLOVER, win_size);
4134 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4135 m_pRouteRolloverWin->IsActive(true);
4136 b_need_refresh = true;
4137 showRouteRollover = true;
4138 break;
4139 }
4140 } else
4141 node = node->GetNext();
4142 }
4143 } else {
4144 // Is the cursor still in select radius, and not timed out?
4145 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4146 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4147 m_pRolloverRouteSeg))
4148 showRouteRollover = false;
4149 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4150 showRouteRollover = false;
4151 else
4152 showRouteRollover = true;
4153 }
4154
4155 // If currently creating a route, do not show this rollover window
4156 if (m_routeState) showRouteRollover = false;
4157
4158 // Similar for AIS target rollover window
4159 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4160 showRouteRollover = false;
4161
4162 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4163 !showRouteRollover) {
4164 m_pRouteRolloverWin->IsActive(false);
4165 m_pRolloverRouteSeg = NULL;
4166 m_pRouteRolloverWin->Destroy();
4167 m_pRouteRolloverWin = NULL;
4168 b_need_refresh = true;
4169 } else if (m_pRouteRolloverWin && showRouteRollover) {
4170 m_pRouteRolloverWin->IsActive(true);
4171 b_need_refresh = true;
4172 }
4173
4174 // Now the Track info rollover
4175 // Show the track segment info
4176 bool showTrackRollover = false;
4177
4178 if (NULL == m_pRolloverTrackSeg) {
4179 // Get a list of all selectable sgements, and search for the first
4180 // visible segment as the rollover target.
4181
4182 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4183 SelectableItemList SelList = pSelect->FindSelectionList(
4184 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4185 wxSelectableItemListNode *node = SelList.GetFirst();
4186 while (node) {
4187 SelectItem *pFindSel = node->GetData();
4188
4189 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4190
4191 if (pt && pt->IsVisible()) {
4192 m_pRolloverTrackSeg = pFindSel;
4193 showTrackRollover = true;
4194
4195 if (NULL == m_pTrackRolloverWin) {
4196 m_pTrackRolloverWin = new RolloverWin(this, 10);
4197 m_pTrackRolloverWin->IsActive(false);
4198 }
4199
4200 if (!m_pTrackRolloverWin->IsActive()) {
4201 wxString s;
4202 TrackPoint *segShow_point_a =
4203 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4204 TrackPoint *segShow_point_b =
4205 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4206
4207 double brg, dist;
4208 DistanceBearingMercator(
4209 segShow_point_b->m_lat, segShow_point_b->m_lon,
4210 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4211
4212 if (!pt->m_bIsInLayer)
4213 s.Append(_("Track") + _T(": "));
4214 else
4215 s.Append(_("Layer Track: "));
4216
4217 if (pt->GetName().IsEmpty())
4218 s.Append(_("(unnamed)"));
4219 else
4220 s.Append(pt->GetName());
4221 double tlenght = pt->Length();
4222 s << _T("\n") << _("Total Track: ")
4223 << FormatDistanceAdaptive(tlenght);
4224 if (pt->GetLastPoint()->GetTimeString() &&
4225 pt->GetPoint(0)->GetTimeString()) {
4226 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4227 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4228 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4229 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4230 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4231 s << wxString::Format(_T(" %.1f "), (float)(tlenght / htime))
4232 << getUsrSpeedUnit();
4233 s << wxString(htime > 24. ? ttime.Format(_T(" %Dd %H:%M"))
4234 : ttime.Format(_T(" %H:%M")));
4235 }
4236 }
4237
4238 if (g_bShowTrackPointTime &&
4239 strlen(segShow_point_b->GetTimeString())) {
4240 wxString stamp = segShow_point_b->GetTimeString();
4241 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4242 if (timestamp.IsValid()) {
4243 // Format track rollover timestamp to OCPN global TZ setting
4246 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4247 }
4248 s << _T("\n") << _("Segment Created: ") << stamp;
4249 }
4250
4251 s << _T("\n");
4252 if (g_bShowTrue)
4253 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4254 0x00B0);
4255
4256 if (g_bShowMag) {
4257 double latAverage =
4258 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4259 double lonAverage =
4260 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4261 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4262
4263 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4264 0x00B0);
4265 }
4266
4267 s << FormatDistanceAdaptive(dist);
4268
4269 if (segShow_point_a->GetTimeString() &&
4270 segShow_point_b->GetTimeString()) {
4271 wxDateTime apoint = segShow_point_a->GetCreateTime();
4272 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4273 if (apoint.IsValid() && bpoint.IsValid()) {
4274 double segmentSpeed = toUsrSpeed(
4275 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4276 s << wxString::Format(_T(" %.1f "), (float)segmentSpeed)
4277 << getUsrSpeedUnit();
4278 }
4279 }
4280
4281 m_pTrackRolloverWin->SetString(s);
4282
4283 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4284 LEG_ROLLOVER, win_size);
4285 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4286 m_pTrackRolloverWin->IsActive(true);
4287 b_need_refresh = true;
4288 showTrackRollover = true;
4289 break;
4290 }
4291 } else
4292 node = node->GetNext();
4293 }
4294 } else {
4295 // Is the cursor still in select radius, and not timed out?
4296 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4297 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4298 m_pRolloverTrackSeg))
4299 showTrackRollover = false;
4300 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4301 showTrackRollover = false;
4302 else
4303 showTrackRollover = true;
4304 }
4305
4306 // Similar for AIS target rollover window
4307 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4308 showTrackRollover = false;
4309
4310 // Similar for route rollover window
4311 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4312 showTrackRollover = false;
4313
4314 // TODO We onlt show tracks on primary canvas....
4315 // if(!IsPrimaryCanvas())
4316 // showTrackRollover = false;
4317
4318 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4319 !showTrackRollover) {
4320 m_pTrackRolloverWin->IsActive(false);
4321 m_pRolloverTrackSeg = NULL;
4322 m_pTrackRolloverWin->Destroy();
4323 m_pTrackRolloverWin = NULL;
4324 b_need_refresh = true;
4325 } else if (m_pTrackRolloverWin && showTrackRollover) {
4326 m_pTrackRolloverWin->IsActive(true);
4327 b_need_refresh = true;
4328 }
4329
4330 if (b_need_refresh) Refresh();
4331}
4332
4333void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4334 if ((GetShowENCLights() || m_bsectors_shown) &&
4335 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4336 extendedSectorLegs)) {
4337 if (!m_bsectors_shown) {
4338 ReloadVP(false);
4339 m_bsectors_shown = true;
4340 }
4341 } else {
4342 if (m_bsectors_shown) {
4343 ReloadVP(false);
4344 m_bsectors_shown = false;
4345 }
4346 }
4347
4348// This is here because GTK status window update is expensive..
4349// cairo using pango rebuilds the font every time so is very
4350// inefficient
4351// Anyway, only update the status bar when this timer expires
4352#if defined(__WXGTK__) || defined(__WXQT__)
4353 {
4354 // Check the absolute range of the cursor position
4355 // There could be a window wherein the chart geoereferencing is not
4356 // valid....
4357 double cursor_lat, cursor_lon;
4358 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4359
4360 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4361 while (cursor_lon < -180.) cursor_lon += 360.;
4362
4363 while (cursor_lon > 180.) cursor_lon -= 360.;
4364
4365 SetCursorStatus(cursor_lat, cursor_lon);
4366 }
4367 }
4368#endif
4369}
4370
4371void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4372 if (!parent_frame->m_pStatusBar) return;
4373
4374 wxString s1;
4375 s1 += _T(" ");
4376 s1 += toSDMM(1, cursor_lat);
4377 s1 += _T(" ");
4378 s1 += toSDMM(2, cursor_lon);
4379
4380 if (STAT_FIELD_CURSOR_LL >= 0)
4381 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4382
4383 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4384
4385 double brg, dist;
4386 wxString sm;
4387 wxString st;
4388 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4389 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4390 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4391
4392 wxString s = st + sm;
4393 s << FormatDistanceAdaptive(dist);
4394
4395 // CUSTOMIZATION - LIVE ETA OPTION
4396 // -------------------------------------------------------
4397 // Calculate an "live" ETA based on route starting from the current
4398 // position of the boat and goes to the cursor of the mouse.
4399 // In any case, an standard ETA will be calculated with a default speed
4400 // of the boat to give an estimation of the route (in particular if GPS
4401 // is off).
4402
4403 // Display only if option "live ETA" is selected in Settings > Display >
4404 // General.
4405 if (g_bShowLiveETA) {
4406 float realTimeETA;
4407 float boatSpeed;
4408 float boatSpeedDefault = g_defaultBoatSpeed;
4409
4410 // Calculate Estimate Time to Arrival (ETA) in minutes
4411 // Check before is value not closed to zero (it will make an very big
4412 // number...)
4413 if (!std::isnan(gSog)) {
4414 boatSpeed = gSog;
4415 if (boatSpeed < 0.5) {
4416 realTimeETA = 0;
4417 } else {
4418 realTimeETA = dist / boatSpeed * 60;
4419 }
4420 } else {
4421 realTimeETA = 0;
4422 }
4423
4424 // Add space after distance display
4425 s << " ";
4426 // Display ETA
4427 s << minutesToHoursDays(realTimeETA);
4428
4429 // In any case, display also an ETA with default speed at 6knts
4430
4431 s << " [@";
4432 s << wxString::Format(_T("%d"), (int)toUsrSpeed(boatSpeedDefault, -1));
4433 s << wxString::Format(_T("%s"), getUsrSpeedUnit(-1));
4434 s << " ";
4435 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4436 s << "]";
4437 }
4438 // END OF - LIVE ETA OPTION
4439
4440 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4441}
4442
4443// CUSTOMIZATION - FORMAT MINUTES
4444// -------------------------------------------------------
4445// New function to format minutes into a more readable format:
4446// * Hours + minutes, or
4447// * Days + hours.
4448wxString minutesToHoursDays(float timeInMinutes) {
4449 wxString s;
4450
4451 if (timeInMinutes == 0) {
4452 s << "--min";
4453 }
4454
4455 // Less than 60min, keep time in minutes
4456 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4457 s << wxString::Format(_T("%d"), (int)timeInMinutes);
4458 s << "min";
4459 }
4460
4461 // Between 1h and less than 24h, display time in hours, minutes
4462 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4463 int hours;
4464 int min;
4465 hours = (int)timeInMinutes / 60;
4466 min = (int)timeInMinutes % 60;
4467
4468 if (min == 0) {
4469 s << wxString::Format(_T("%d"), hours);
4470 s << "h";
4471 } else {
4472 s << wxString::Format(_T("%d"), hours);
4473 s << "h";
4474 s << wxString::Format(_T("%d"), min);
4475 s << "min";
4476 }
4477
4478 }
4479
4480 // More than 24h, display time in days, hours
4481 else if (timeInMinutes > 24 * 60) {
4482 int days;
4483 int hours;
4484 days = (int)(timeInMinutes / 60) / 24;
4485 hours = (int)(timeInMinutes / 60) % 24;
4486
4487 if (hours == 0) {
4488 s << wxString::Format(_T("%d"), days);
4489 s << "d";
4490 } else {
4491 s << wxString::Format(_T("%d"), days);
4492 s << "d";
4493 s << wxString::Format(_T("%d"), hours);
4494 s << "h";
4495 }
4496 }
4497
4498 return s;
4499}
4500
4501// END OF CUSTOMIZATION - FORMAT MINUTES
4502// Thanks open source code ;-)
4503// -------------------------------------------------------
4504
4505void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4506 double clat, clon;
4507 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4508 *lat = clat;
4509 *lon = clon;
4510}
4511
4512void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4513 wxPoint2DDouble *r) {
4514 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4515}
4516
4518 double rlon, wxPoint2DDouble *r) {
4519 // If the Current Chart is a raster chart, and the
4520 // requested lat/long is within the boundaries of the chart,
4521 // and the VP is not rotated,
4522 // then use the embedded BSB chart georeferencing algorithm
4523 // for greater accuracy
4524 // Additionally, use chart embedded georef if the projection is TMERC
4525 // i.e. NOT MERCATOR and NOT POLYCONIC
4526
4527 // If for some reason the chart rejects the request by returning an error,
4528 // then fall back to Viewport Projection estimate from canvas parameters
4529 if (!g_bopengl && m_singleChart &&
4530 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4531 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4532 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4533 (m_singleChart->GetChartProjectionType() !=
4534 PROJECTION_TRANSVERSE_MERCATOR) &&
4535 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4536 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4537 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4538 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4539 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4540 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4541 // Cur_BSB_Ch->GetCOVRTablenPoints
4542 // ( 0 ), rlon,
4543 // rlat );
4544 // bInside = true;
4545 // if ( bInside )
4546 if (Cur_BSB_Ch) {
4547 // This is a Raster chart....
4548 // If the VP is changing, the raster chart parameters may not yet be
4549 // setup So do that before accessing the chart's embedded
4550 // georeferencing
4551 Cur_BSB_Ch->SetVPRasterParms(vp);
4552 double rpixxd, rpixyd;
4553 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4554 r->m_x = rpixxd;
4555 r->m_y = rpixyd;
4556 return;
4557 }
4558 }
4559 }
4560
4561 // if needed, use the VPoint scaling estimator,
4562 *r = vp.GetDoublePixFromLL(rlat, rlon);
4563}
4564
4565// This routine might be deleted and all of the rendering improved
4566// to have floating point accuracy
4567bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4568 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4569}
4570
4571bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4572 wxPoint *r) {
4573 wxPoint2DDouble p;
4574 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4575
4576 // some projections give nan values when invisible values (other side of
4577 // world) are requested we should stop using integer coordinates or return
4578 // false here (and test it everywhere)
4579 if (std::isnan(p.m_x)) {
4580 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4581 return false;
4582 }
4583
4584 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4585 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4586 else
4587 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4588
4589 return true;
4590}
4591
4592void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4593 double &lon) {
4594 // If the Current Chart is a raster chart, and the
4595 // requested x,y is within the boundaries of the chart,
4596 // and the VP is not rotated,
4597 // then use the embedded BSB chart georeferencing algorithm
4598 // for greater accuracy
4599 // Additionally, use chart embedded georef if the projection is TMERC
4600 // i.e. NOT MERCATOR and NOT POLYCONIC
4601
4602 // If for some reason the chart rejects the request by returning an error,
4603 // then fall back to Viewport Projection estimate from canvas parameters
4604 bool bUseVP = true;
4605
4606 if (!g_bopengl && m_singleChart &&
4607 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4608 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4609 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4610 (m_singleChart->GetChartProjectionType() !=
4611 PROJECTION_TRANSVERSE_MERCATOR) &&
4612 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4613 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4614 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4615 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4616
4617 // TODO maybe need iterative process to validate bInside
4618 // first pass is mercator, then check chart boundaries
4619
4620 if (Cur_BSB_Ch) {
4621 // This is a Raster chart....
4622 // If the VP is changing, the raster chart parameters may not yet be
4623 // setup So do that before accessing the chart's embedded
4624 // georeferencing
4625 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4626
4627 double slat, slon;
4628 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4629 lat = slat;
4630
4631 if (slon < -180.)
4632 slon += 360.;
4633 else if (slon > 180.)
4634 slon -= 360.;
4635
4636 lon = slon;
4637 bUseVP = false;
4638 }
4639 }
4640 }
4641
4642 // if needed, use the VPoint scaling estimator
4643 if (bUseVP) {
4644 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4645 }
4646}
4647
4649 StopMovement();
4650 DoZoomCanvas(factor, false);
4651 extendedSectorLegs.clear();
4652}
4653
4654void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4655 bool stoptimer) {
4656 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4657
4658 if (g_bsmoothpanzoom) {
4659 if (StartTimedMovement(stoptimer)) {
4660 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4661 m_zoom_factor = factor;
4662 }
4663
4664 m_zoom_target = VPoint.chart_scale / factor;
4665 } else {
4666 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4667
4668 DoZoomCanvas(factor, can_zoom_to_cursor);
4669 }
4670
4671 extendedSectorLegs.clear();
4672}
4673
4674void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4675 // possible on startup
4676 if (!ChartData) return;
4677 if (!m_pCurrentStack) return;
4678
4679 /* TODO: queue the quilted loading code to a background thread
4680 so yield is never called from here, and also rendering is not delayed */
4681
4682 // Cannot allow Yield() re-entrancy here
4683 if (m_bzooming) return;
4684 m_bzooming = true;
4685
4686 double old_ppm = GetVP().view_scale_ppm;
4687
4688 // Capture current cursor position for zoom to cursor
4689 double zlat = m_cursor_lat;
4690 double zlon = m_cursor_lon;
4691
4692 double proposed_scale_onscreen =
4693 GetVP().chart_scale /
4694 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4695 bool b_do_zoom = false;
4696
4697 if (factor > 1) {
4698 b_do_zoom = true;
4699
4700 // double zoom_factor = factor;
4701
4702 ChartBase *pc = NULL;
4703
4704 if (!VPoint.b_quilt) {
4705 pc = m_singleChart;
4706 } else {
4707 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4708 if (new_db_index >= 0)
4709 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4710 else { // for whatever reason, no reference chart is known
4711 // Choose the smallest scale chart on the current stack
4712 // and then adjust for scale range
4713 int current_ref_stack_index = -1;
4714 if (m_pCurrentStack->nEntry) {
4715 int trial_index =
4716 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4717 m_pQuilt->SetReferenceChart(trial_index);
4718 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4719 if (new_db_index >= 0)
4720 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4721 }
4722 }
4723
4724 if (m_pCurrentStack)
4725 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4726 new_db_index); // highlite the correct bar entry
4727 }
4728
4729 if (pc) {
4730 // double target_scale_ppm = GetVPScale() * zoom_factor;
4731 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4732 // target_scale_ppm;
4733
4734 // Query the chart to determine the appropriate zoom range
4735 double min_allowed_scale =
4736 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4737
4738 if (proposed_scale_onscreen < min_allowed_scale) {
4739 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4740 m_zoom_factor = 1; /* stop zooming */
4741 b_do_zoom = false;
4742 } else
4743 proposed_scale_onscreen = min_allowed_scale;
4744 }
4745
4746 } else {
4747 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4748 }
4749
4750 } else if (factor < 1) {
4751 b_do_zoom = true;
4752
4753 ChartBase *pc = NULL;
4754
4755 bool b_smallest = false;
4756
4757 if (!VPoint.b_quilt) { // not quilted
4758 pc = m_singleChart;
4759
4760 if (pc) {
4761 // If m_singleChart is not on the screen, unbound the zoomout
4762 LLBBox viewbox = VPoint.GetBBox();
4763 // BoundingBox chart_box;
4764 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4765 double max_allowed_scale;
4766
4767 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4768
4769 // We can allow essentially unbounded zoomout in single chart mode
4770 // if( ChartData->GetDBBoundingBox( current_index,
4771 // &chart_box ) &&
4772 // !viewbox.IntersectOut( chart_box ) )
4773 // // Clamp the minimum scale zoom-out to the value
4774 // specified by the chart max_allowed_scale =
4775 // wxMin(max_allowed_scale, 4.0 *
4776 // pc->GetNormalScaleMax(
4777 // GetCanvasScaleFactor(),
4778 // GetCanvasWidth() ) );
4779 if (proposed_scale_onscreen > max_allowed_scale) {
4780 m_zoom_factor = 1; /* stop zooming */
4781 proposed_scale_onscreen = max_allowed_scale;
4782 }
4783 }
4784
4785 } else {
4786 int new_db_index = m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4787 if (new_db_index >= 0)
4788 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4789
4790 if (m_pCurrentStack)
4791 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4792 new_db_index); // highlite the correct bar entry
4793
4794 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4795
4796 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4797 proposed_scale_onscreen =
4798 wxMin(proposed_scale_onscreen,
4799 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4800 }
4801
4802 // set a minimum scale
4803 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4804 m_absolute_min_scale_ppm)
4805 b_do_zoom = false;
4806 }
4807
4808 double new_scale =
4809 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4810
4811 if (b_do_zoom) {
4812 // Disable ZTC if lookahead is ON, and currently b_follow is active
4813 bool b_allow_ztc = true;
4814 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4815 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4816 if (m_bLookAhead) {
4817 double brg, distance;
4818 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4819 &distance);
4820 dir_to_shift = brg;
4821 meters_to_shift = distance * 1852;
4822 }
4823 // Arrange to combine the zoom and pan into one operation for smoother
4824 // appearance
4825 SetVPScale(new_scale, false); // adjust, but deferred refresh
4826 wxPoint r;
4827 GetCanvasPointPix(zlat, zlon, &r);
4828 // this will emit the Refresh()
4829 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4830 } else {
4831 SetVPScale(new_scale);
4832 if (m_bFollow) DoCanvasUpdate();
4833 }
4834 }
4835
4836 m_bzooming = false;
4837}
4838int rot;
4839void ChartCanvas::RotateCanvas(double dir) {
4840 // SetUpMode(NORTH_UP_MODE);
4841
4842 if (g_bsmoothpanzoom) {
4843 if (StartTimedMovement()) {
4844 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4845 m_rotation_speed = dir * 60;
4846 }
4847 } else {
4848 double speed = dir * 10;
4849 if (m_modkeys == wxMOD_ALT) speed /= 20;
4850 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4851 }
4852}
4853
4854void ChartCanvas::DoRotateCanvas(double rotation) {
4855 while (rotation < 0) rotation += 2 * PI;
4856 while (rotation > 2 * PI) rotation -= 2 * PI;
4857
4858 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4859
4860 SetVPRotation(rotation);
4861 parent_frame->UpdateRotationState(VPoint.rotation);
4862}
4863
4864void ChartCanvas::DoTiltCanvas(double tilt) {
4865 while (tilt < 0) tilt = 0;
4866 while (tilt > .95) tilt = .95;
4867
4868 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4869
4870 VPoint.tilt = tilt;
4871 Refresh(false);
4872}
4873
4874void ChartCanvas::TogglebFollow(void) {
4875 if (!m_bFollow)
4876 SetbFollow();
4877 else
4878 ClearbFollow();
4879}
4880
4881void ChartCanvas::ClearbFollow(void) {
4882 m_bFollow = false; // update the follow flag
4883
4884 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4885
4886 UpdateFollowButtonState();
4887
4888 DoCanvasUpdate();
4889 ReloadVP();
4890 parent_frame->SetChartUpdatePeriod();
4891}
4892
4893void ChartCanvas::SetbFollow(void) {
4894 // Is the OWNSHIP on-screen?
4895 // If not, then reset the OWNSHIP offset to 0 (center screen)
4896 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4897 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4898 m_OSoffsetx = 0;
4899 m_OSoffsety = 0;
4900 }
4901
4902 // Apply the present b_follow offset values to ship position
4903 wxPoint2DDouble p;
4904 GetDoubleCanvasPointPix(gLat, gLon, &p);
4905 p.m_x += m_OSoffsetx;
4906 p.m_y -= m_OSoffsety;
4907
4908 // compute the target center screen lat/lon
4909 double dlat, dlon;
4910 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4911
4912 JumpToPosition(dlat, dlon, GetVPScale());
4913 m_bFollow = true;
4914
4915 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4916 UpdateFollowButtonState();
4917
4918 if (!g_bSmoothRecenter) {
4919 DoCanvasUpdate();
4920 ReloadVP();
4921 }
4922 parent_frame->SetChartUpdatePeriod();
4923}
4924
4925void ChartCanvas::UpdateFollowButtonState(void) {
4926 if (m_muiBar) {
4927 if (!m_bFollow)
4928 m_muiBar->SetFollowButtonState(0);
4929 else {
4930 if (m_bLookAhead)
4931 m_muiBar->SetFollowButtonState(2);
4932 else
4933 m_muiBar->SetFollowButtonState(1);
4934 }
4935 }
4936
4937#ifdef __ANDROID__
4938 if (!m_bFollow)
4939 androidSetFollowTool(0);
4940 else {
4941 if (m_bLookAhead)
4942 androidSetFollowTool(2);
4943 else
4944 androidSetFollowTool(1);
4945 }
4946#endif
4947}
4948
4949void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4950 if (g_bSmoothRecenter && !m_routeState) {
4951 if (StartSmoothJump(lat, lon, scale_ppm))
4952 return;
4953 else {
4954 // move closer to the target destination, and try again
4955 double gcDist, gcBearingEnd;
4956 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4957 &gcBearingEnd);
4958 gcBearingEnd += 180;
4959 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4960 GetCanvasWidth() / GetVPScale(); // meters
4961 double lon_offset =
4962 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4963 double new_lat = lat + (lat_offset / (1852 * 60));
4964 double new_lon = lon + (lon_offset / (1852 * 60));
4965 SetViewPoint(new_lat, new_lon);
4966 ReloadVP();
4967 StartSmoothJump(lat, lon, scale_ppm);
4968 return;
4969 }
4970 }
4971
4972 if (lon > 180.0) lon -= 360.0;
4973 m_vLat = lat;
4974 m_vLon = lon;
4975 StopMovement();
4976 m_bFollow = false;
4977
4978 if (!GetQuiltMode()) {
4979 double skew = 0;
4980 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4981 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4982 } else {
4983 if (scale_ppm != GetVPScale()) {
4984 // XXX should be done in SetViewPoint
4985 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4986 AdjustQuiltRefChart();
4987 }
4988 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4989 }
4990
4991 ReloadVP();
4992
4993 UpdateFollowButtonState();
4994
4995 // TODO
4996 // if( g_pi_manager ) {
4997 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4998 // }
4999}
5000
5001bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
5002 // Check distance to jump, in pixels at current chart scale
5003 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5004 // width.
5005 double gcDist;
5006 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5007 double distance_pixels = gcDist * GetVPScale();
5008 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5009 // Jump is too far, try again
5010 return false;
5011 }
5012
5013 // Save where we're coming from
5014 m_startLat = m_vLat;
5015 m_startLon = m_vLon;
5016 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5017
5018 // Save where we want to end up
5019 m_endLat = lat;
5020 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5021 m_endScale = scale_ppm;
5022
5023 // Setup timing
5024 m_animationDuration = 600; // ms
5025 m_animationStart = wxGetLocalTimeMillis();
5026
5027 // Stop any previous movement, ensure no conflicts
5028 StopMovement();
5029 m_bFollow = false;
5030
5031 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5032 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5033 m_animationActive = true;
5034
5035 return true;
5036}
5037
5038void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5039 // Calculate time fraction from 0..1
5040 wxLongLong now = wxGetLocalTimeMillis();
5041 double elapsed = (now - m_animationStart).ToDouble();
5042 double t = elapsed / m_animationDuration.ToDouble();
5043 if (t > 1.0) t = 1.0;
5044
5045 // Ease function for smoother movement
5046 double e = easeOutCubic(t);
5047
5048 // Interpolate lat/lon/scale
5049 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5050 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5051 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5052
5053 // Update viewpoint
5054 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5055 // portion)
5056 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5057 ReloadVP();
5058
5059 // If we reached the end, stop the timer and finalize
5060 if (t >= 1.0) {
5061 m_easeTimer.Stop();
5062 m_animationActive = false;
5063 UpdateFollowButtonState();
5064 ZoomCanvasSimple(1.0001);
5065 DoCanvasUpdate();
5066 ReloadVP();
5067 }
5068}
5069
5070bool ChartCanvas::PanCanvas(double dx, double dy) {
5071 if (!ChartData) return false;
5072
5073 if (g_btouch) {
5074 // Stop bfollow state, without a refresh
5075 m_bFollow = false; // update the follow flag
5076 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
5077 UpdateFollowButtonState();
5078 // Clear the bfollow offset
5079 m_OSoffsetx = 0;
5080 m_OSoffsety = 0;
5081 }
5082
5083 extendedSectorLegs.clear();
5084
5085 // double clat = VPoint.clat, clon = VPoint.clon;
5086 double dlat, dlon;
5087 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5088
5089 int iters = 0;
5090 for (;;) {
5091 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5092
5093 if (iters++ > 5) return false;
5094 if (!std::isnan(dlat)) break;
5095
5096 dx *= .5, dy *= .5;
5097 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5098 }
5099
5100 // avoid overshooting the poles
5101 if (dlat > 90)
5102 dlat = 90;
5103 else if (dlat < -90)
5104 dlat = -90;
5105
5106 if (dlon > 360.) dlon -= 360.;
5107 if (dlon < -360.) dlon += 360.;
5108
5109 // This should not really be necessary, but round-trip georef on some
5110 // charts is not perfect, So we can get creep on repeated unidimensional
5111 // pans, and corrupt chart cacheing.......
5112
5113 // But this only works on north-up projections
5114 // TODO: can we remove this now?
5115 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5116 // .001 ) ) {
5117 //
5118 // if( dx == 0 ) dlon = clon;
5119 // if( dy == 0 ) dlat = clat;
5120 // }
5121
5122 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5123
5124 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5125
5126 if (VPoint.b_quilt) {
5127 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5128 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5129 // Tweak the scale slightly for a new ref chart
5130 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5131 if (pc) {
5132 double tweak_scale_ppm =
5133 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5134 SetVPScale(tweak_scale_ppm);
5135 }
5136 }
5137
5138 if (new_ref_dbIndex == -1) {
5139#pragma GCC diagnostic push
5140#pragma GCC diagnostic ignored "-Warray-bounds"
5141 // The compiler sees a -1 index being used. Does not happen, though.
5142
5143 // for whatever reason, no reference chart is known
5144 // Probably panned out of the coverage region
5145 // If any charts are anywhere on-screen, choose the smallest
5146 // scale chart on the screen to be a new reference chart.
5147 int trial_index = -1;
5148 if (m_pCurrentStack->nEntry) {
5149 int trial_index =
5150 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5151 }
5152
5153 if (trial_index < 0) {
5154 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5155 if (full_screen_array.size())
5156 trial_index = full_screen_array[full_screen_array.size() - 1];
5157 }
5158
5159 if (trial_index >= 0) {
5160 m_pQuilt->SetReferenceChart(trial_index);
5161 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5162 VPoint.rotation);
5163 ReloadVP();
5164 }
5165#pragma GCC diagnostic pop
5166 }
5167 }
5168
5169 // Turn off bFollow only if the ownship has left the screen
5170 if (m_bFollow) {
5171 double offx, offy;
5172 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5173
5174 double offset_angle = atan2(offy, offx);
5175 double offset_distance = sqrt((offy * offy) + (offx * offx));
5176 double chart_angle = GetVPRotation();
5177 double target_angle = chart_angle - offset_angle;
5178 double d_east_mod = offset_distance * cos(target_angle);
5179 double d_north_mod = offset_distance * sin(target_angle);
5180
5181 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5182 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5183
5184 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5185 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5186 m_bFollow = false; // update the follow flag
5187 UpdateFollowButtonState();
5188 }
5189 }
5190
5191 Refresh(false);
5192
5193 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5194
5195 return true;
5196}
5197
5198void ChartCanvas::ReloadVP(bool b_adjust) {
5199 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5200
5201 LoadVP(VPoint, b_adjust);
5202}
5203
5204void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5205#ifdef ocpnUSE_GL
5206 if (g_bopengl && m_glcc) {
5207 m_glcc->Invalidate();
5208 if (m_glcc->GetSize() != GetSize()) {
5209 m_glcc->SetSize(GetSize());
5210 }
5211 } else
5212#endif
5213 {
5214 m_cache_vp.Invalidate();
5215 m_bm_cache_vp.Invalidate();
5216 }
5217
5218 VPoint.Invalidate();
5219
5220 if (m_pQuilt) m_pQuilt->Invalidate();
5221
5222 // Make sure that the Selected Group is sensible...
5223 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5224 // m_groupIndex = 0;
5225 // if( !CheckGroup( m_groupIndex ) )
5226 // m_groupIndex = 0;
5227
5228 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5229 vp.m_projection_type, b_adjust);
5230}
5231
5232void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5233 m_pQuilt->SetReferenceChart(dbIndex);
5234 VPoint.Invalidate();
5235 m_pQuilt->Invalidate();
5236}
5237
5238double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5239 if (m_pQuilt)
5240 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5241 else
5242 return vp.view_scale_ppm;
5243}
5244
5245// Verify and adjust the current reference chart,
5246// so that it will not lead to excessive overzoom or underzoom onscreen
5247int ChartCanvas::AdjustQuiltRefChart() {
5248 int ret = -1;
5249 if (m_pQuilt) {
5250 wxASSERT(ChartData);
5251 ChartBase *pc =
5252 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5253 if (pc) {
5254 double min_ref_scale =
5255 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5256 double max_ref_scale =
5257 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5258
5259 if (VPoint.chart_scale < min_ref_scale) {
5260 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5261 } else if (VPoint.chart_scale > max_ref_scale) {
5262 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5263 } else {
5264 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5265
5266 int ref_family = pc->GetChartFamily();
5267
5268 if (!brender_ok) {
5269 unsigned int target_stack_index = 0;
5270 int target_stack_index_check =
5271 m_pQuilt->GetExtendedStackIndexArray()
5272 [m_pQuilt->GetRefChartdbIndex()]; // Lookup
5273
5274 if (wxNOT_FOUND != target_stack_index_check)
5275 target_stack_index = target_stack_index_check;
5276
5277 int extended_array_count =
5278 m_pQuilt->GetExtendedStackIndexArray().size();
5279 while ((!brender_ok) &&
5280 ((int)target_stack_index < (extended_array_count - 1))) {
5281 target_stack_index++;
5282 int test_db_index =
5283 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5284
5285 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5286 IsChartQuiltableRef(test_db_index)) {
5287 // open the target, and check the min_scale
5288 ChartBase *ptest_chart =
5289 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5290 if (ptest_chart) {
5291 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5292 }
5293 }
5294 }
5295
5296 if (brender_ok) { // found a better reference chart
5297 int new_db_index =
5298 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5299 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5300 IsChartQuiltableRef(new_db_index)) {
5301 m_pQuilt->SetReferenceChart(new_db_index);
5302 ret = new_db_index;
5303 } else
5304 ret = m_pQuilt->GetRefChartdbIndex();
5305 } else
5306 ret = m_pQuilt->GetRefChartdbIndex();
5307
5308 } else
5309 ret = m_pQuilt->GetRefChartdbIndex();
5310 }
5311 } else
5312 ret = -1;
5313 }
5314
5315 return ret;
5316}
5317
5318void ChartCanvas::UpdateCanvasOnGroupChange(void) {
5319 delete m_pCurrentStack;
5320 m_pCurrentStack = new ChartStack;
5321 wxASSERT(ChartData);
5322 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5323 m_groupIndex);
5324
5325 if (m_pQuilt) {
5326 m_pQuilt->Compose(VPoint);
5327 SetFocus();
5328 }
5329}
5330
5331bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5332 double latNE, double lonNE) {
5333 // Center Point
5334 double latc = (latSW + latNE) / 2.0;
5335 double lonc = (lonSW + lonNE) / 2.0;
5336
5337 // Get scale in ppm (latitude)
5338 double ne_easting, ne_northing;
5339 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5340
5341 double sw_easting, sw_northing;
5342 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5343
5344 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5345
5346 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5347}
5348
5349bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5350 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5351 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5352}
5353
5354bool ChartCanvas::SetVPProjection(int projection) {
5355 if (!g_bopengl) // alternative projections require opengl
5356 return false;
5357
5358 // the view scale varies depending on geographic location and projection
5359 // rescale to keep the relative scale on the screen the same
5360 double prev_true_scale_ppm = m_true_scale_ppm;
5361 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5362 VPoint.skew, VPoint.rotation, projection) &&
5363 SetVPScale(wxMax(
5364 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5365 m_absolute_min_scale_ppm));
5366}
5367
5368bool ChartCanvas::SetViewPoint(double lat, double lon) {
5369 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5370 VPoint.rotation);
5371}
5372
5373bool ChartCanvas::SetVPRotation(double angle) {
5374 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5375 VPoint.skew, angle);
5376}
5377bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5378 double skew, double rotation, int projection,
5379 bool b_adjust, bool b_refresh) {
5380 bool b_ret = false;
5381 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5382 skew -= 2 * PI;
5383 // Any sensible change?
5384 if (VPoint.IsValid()) {
5385 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5386 (fabs(VPoint.skew - skew) < 1e-9) &&
5387 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5388 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5389 (VPoint.m_projection_type == projection ||
5390 projection == PROJECTION_UNKNOWN))
5391 return false;
5392 }
5393 if (VPoint.m_projection_type != projection)
5394 VPoint.InvalidateTransformCache(); // invalidate
5395
5396 // Take a local copy of the last viewport
5397 ViewPort last_vp = VPoint;
5398
5399 VPoint.skew = skew;
5400 VPoint.clat = lat;
5401 VPoint.clon = lon;
5402 VPoint.rotation = rotation;
5403 VPoint.view_scale_ppm = scale_ppm;
5404 if (projection != PROJECTION_UNKNOWN)
5405 VPoint.SetProjectionType(projection);
5406 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5407 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5408
5409 // don't allow latitude above 88 for mercator (90 is infinity)
5410 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5411 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5412 if (VPoint.clat > 89.5)
5413 VPoint.clat = 89.5;
5414 else if (VPoint.clat < -89.5)
5415 VPoint.clat = -89.5;
5416 }
5417
5418 // don't zoom out too far for transverse mercator polyconic until we resolve
5419 // issues
5420 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5421 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5422 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5423
5424 // SetVPRotation(rotation);
5425
5426 if (!g_bopengl) // tilt is not possible without opengl
5427 VPoint.tilt = 0;
5428
5429 if ((VPoint.pix_width <= 0) ||
5430 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5431 return false;
5432
5433 bool bwasValid = VPoint.IsValid();
5434 VPoint.Validate(); // Mark this ViewPoint as OK
5435
5436 // Has the Viewport scale changed? If so, invalidate the vp
5437 if (last_vp.view_scale_ppm != scale_ppm) {
5438 m_cache_vp.Invalidate();
5439 InvalidateGL();
5440 }
5441
5442 // A preliminary value, may be tweaked below
5443 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5444
5445 // recompute cursor position
5446 // and send to interested plugins if the mouse is actually in this window
5447 int mouseX = mouse_x;
5448 int mouseY = mouse_y;
5449 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5450 (mouseY < VPoint.pix_height)) {
5451 double lat, lon;
5452 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5453 m_cursor_lat = lat;
5454 m_cursor_lon = lon;
5455 SendCursorLatLonToAllPlugIns(lat, lon);
5456 }
5457
5458 if (!VPoint.b_quilt && m_singleChart) {
5459 VPoint.SetBoxes();
5460
5461 // Allow the chart to adjust the new ViewPort for performance optimization
5462 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5463 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5464
5465 // If there is a sensible change in the chart render, refresh the whole
5466 // screen
5467 if ((!m_cache_vp.IsValid()) ||
5468 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5469 Refresh(false);
5470 b_ret = true;
5471 } else {
5472 wxPoint cp_last, cp_this;
5473 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5474 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5475
5476 if (cp_last != cp_this) {
5477 Refresh(false);
5478 b_ret = true;
5479 }
5480 }
5481 // Create the stack
5482 if (m_pCurrentStack) {
5483 assert(ChartData != 0);
5484 int current_db_index;
5485 current_db_index =
5486 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5487
5488 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5489 m_groupIndex);
5490 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5491 }
5492
5493 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5494 }
5495
5496 // Handle the quilted case
5497 if (VPoint.b_quilt) {
5498 if (last_vp.view_scale_ppm != scale_ppm)
5499 m_pQuilt->InvalidateAllQuiltPatchs();
5500
5501 // Create the quilt
5502 if (ChartData /*&& ChartData->IsValid()*/) {
5503 if (!m_pCurrentStack) return false;
5504
5505 int current_db_index;
5506 current_db_index =
5507 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5508
5509 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5510 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5511
5512 // Check to see if the current quilt reference chart is in the new stack
5513 int current_ref_stack_index = -1;
5514 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5515 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5516 current_ref_stack_index = i;
5517 }
5518
5519 if (g_bFullScreenQuilt) {
5520 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5521 }
5522
5523 // We might need a new Reference Chart
5524 bool b_needNewRef = false;
5525
5526 // If the new stack does not contain the current ref chart....
5527 if ((-1 == current_ref_stack_index) &&
5528 (m_pQuilt->GetRefChartdbIndex() >= 0))
5529 b_needNewRef = true;
5530
5531 // Would the current Ref Chart be excessively underzoomed?
5532 // We need to check this here to be sure, since we cannot know where the
5533 // reference chart was assigned. For instance, the reference chart may
5534 // have been selected from the config file, or from a long jump with a
5535 // chart family switch implicit. Anyway, we check to be sure....
5536 bool renderable = true;
5537 ChartBase *referenceChart =
5538 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5539 if (referenceChart) {
5540 double chartMaxScale = referenceChart->GetNormalScaleMax(
5541 GetCanvasScaleFactor(), GetCanvasWidth());
5542 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5543 }
5544 if (!renderable) b_needNewRef = true;
5545
5546 // Need new refchart?
5547 if (b_needNewRef) {
5548 const ChartTableEntry &cte_ref =
5549 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5550 int target_scale = cte_ref.GetScale();
5551 int target_type = cte_ref.GetChartType();
5552 int candidate_stack_index;
5553
5554 // reset the ref chart in a way that does not lead to excessive
5555 // underzoom, for performance reasons Try to find a chart that is the
5556 // same type, and has a scale of just smaller than the current ref
5557 // chart
5558
5559 candidate_stack_index = 0;
5560 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5561 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5562 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5563 int candidate_scale = cte_candidate.GetScale();
5564 int candidate_type = cte_candidate.GetChartType();
5565
5566 if ((candidate_scale >= target_scale) &&
5567 (candidate_type == target_type)) {
5568 bool renderable = true;
5569 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5570 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5571 if (tentative_referenceChart) {
5572 double chartMaxScale =
5573 tentative_referenceChart->GetNormalScaleMax(
5574 GetCanvasScaleFactor(), GetCanvasWidth());
5575 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5576 }
5577
5578 if (renderable) break;
5579 }
5580
5581 candidate_stack_index++;
5582 }
5583
5584 // If that did not work, look for a chart of just larger scale and
5585 // same type
5586 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5587 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5588 while (candidate_stack_index >= 0) {
5589 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5590 if (idx >= 0) {
5591 const ChartTableEntry &cte_candidate =
5592 ChartData->GetChartTableEntry(idx);
5593 int candidate_scale = cte_candidate.GetScale();
5594 int candidate_type = cte_candidate.GetChartType();
5595
5596 if ((candidate_scale <= target_scale) &&
5597 (candidate_type == target_type))
5598 break;
5599 }
5600 candidate_stack_index--;
5601 }
5602 }
5603
5604 // and if that did not work, chose stack entry 0
5605 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5606 (candidate_stack_index < 0))
5607 candidate_stack_index = 0;
5608
5609 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5610
5611 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5612 }
5613
5614 if (!g_bopengl) {
5615 // Preset the VPoint projection type to match what the quilt projection
5616 // type will be
5617 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5618
5619 // Always keep the default Mercator projection if the reference chart is
5620 // not in the PatchList or the scale is too small for it to render.
5621
5622 bool renderable = true;
5623 ChartBase *referenceChart =
5624 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5625 if (referenceChart) {
5626 double chartMaxScale = referenceChart->GetNormalScaleMax(
5627 GetCanvasScaleFactor(), GetCanvasWidth());
5628 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5629 proj = ChartData->GetDBChartProj(ref_db_index);
5630 } else
5631 proj = PROJECTION_MERCATOR;
5632
5633 VPoint.b_MercatorProjectionOverride =
5634 (m_pQuilt->GetnCharts() == 0 || !renderable);
5635
5636 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5637
5638 VPoint.SetProjectionType(proj);
5639 }
5640
5641 VPoint.SetBoxes();
5642
5643 // If this quilt will be a perceptible delta from the existing quilt,
5644 // then refresh the entire screen
5645 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5646 // Allow the quilt to adjust the new ViewPort for performance
5647 // optimization This will normally be only a fractional (i.e.
5648 // sub-pixel) adjustment...
5649 if (b_adjust) {
5650 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5651 }
5652
5653 // ChartData->ClearCacheInUseFlags();
5654 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5655
5656 // wxStopWatch sw;
5657
5658#ifdef __ANDROID__
5659 // This is an optimization for panning on touch screen systems.
5660 // The quilt composition is deferred until the OnPaint() message gets
5661 // finally removed and processed from the message queue.
5662 // Takes advantage of the fact that touch-screen pan gestures are
5663 // usually short in distance,
5664 // so not requiring a full quilt rebuild until the pan gesture is
5665 // complete.
5666 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5667 // qDebug() << "Force compose";
5668 m_pQuilt->Compose(VPoint);
5669 } else {
5670 m_pQuilt->Invalidate();
5671 }
5672#else
5673 m_pQuilt->Compose(VPoint);
5674#endif
5675
5676 // printf("comp time %ld\n", sw.Time());
5677
5678 // If the extended chart stack has changed, invalidate any cached
5679 // render bitmap
5680 // if(m_pQuilt->GetXStackHash() != hash1) {
5681 // m_bm_cache_vp.Invalidate();
5682 // InvalidateGL();
5683 // }
5684
5685 ChartData->PurgeCacheUnusedCharts(0.7);
5686
5687 if (b_refresh) Refresh(false);
5688
5689 b_ret = true;
5690 }
5691 }
5692
5693 VPoint.skew = 0.; // Quilting supports 0 Skew
5694 } else if (!g_bopengl) {
5695 OcpnProjType projection = PROJECTION_UNKNOWN;
5696 if (m_singleChart) // viewport projection must match chart projection
5697 // without opengl
5698 projection = m_singleChart->GetChartProjectionType();
5699 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5700 VPoint.SetProjectionType(projection);
5701 }
5702
5703 // Has the Viewport projection changed? If so, invalidate the vp
5704 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5705 m_cache_vp.Invalidate();
5706 InvalidateGL();
5707 }
5708
5709 UpdateCanvasControlBar(); // Refresh the Piano
5710
5711 VPoint.chart_scale = 1.0; // fallback default value
5712
5713 /*if (!VPoint.GetBBox().GetValid())*/ VPoint.SetBoxes();
5714
5715 if (VPoint.GetBBox().GetValid()) {
5716 // Update the viewpoint reference scale
5717 if (m_singleChart)
5718 VPoint.ref_scale = m_singleChart->GetNativeScale();
5719 else {
5720#ifdef __ANDROID__
5721 // This is an optimization for panning on touch screen systems.
5722 // See above.
5723 // Quilt might not be fully composed at this point, so for cm93
5724 // the reference scale may not be known.
5725 // In this case, do not update the VP ref_scale.
5726 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5727 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5728 }
5729#else
5730 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5731#endif
5732 }
5733
5734 // Calculate the on-screen displayed actual scale
5735 // by a simple traverse northward from the center point
5736 // of roughly one eighth of the canvas height
5737 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5738
5739 double delta_check =
5740 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5741 delta_check /= 8.;
5742
5743 double check_point = wxMin(89., VPoint.clat);
5744
5745 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5746
5747 double rhumbDist;
5748 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5749 VPoint.clon, 0, &rhumbDist);
5750
5751 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5752 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5753 // Calculate the distance between r1 and r in physical pixels.
5754 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5755 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5756
5757 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5758
5759 // A fall back in case of very high zoom-out, giving delta_y == 0
5760 // which can probably only happen with vector charts
5761 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5762
5763 // Another fallback, for highly zoomed out charts
5764 // This adjustment makes the displayed TrueScale correspond to the
5765 // same algorithm used to calculate the chart zoom-out limit for
5766 // ChartDummy.
5767 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5768
5769 if (m_true_scale_ppm)
5770 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5771 else
5772 VPoint.chart_scale = 1.0;
5773
5774 // Create a nice renderable string
5775 double round_factor = 1000.;
5776 if (VPoint.chart_scale <= 1000.)
5777 round_factor = 10.;
5778 else if (VPoint.chart_scale <= 10000.)
5779 round_factor = 100.;
5780 else if (VPoint.chart_scale <= 100000.)
5781 round_factor = 1000.;
5782
5783 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5784 double retina_coef = 1;
5785#ifdef ocpnUSE_GL
5786#ifdef __WXOSX__
5787 if (g_bopengl) {
5788 retina_coef = GetContentScaleFactor();
5789 }
5790#endif
5791#endif
5792
5793 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5794 // rounded to the nearest 10, 100 or 1000.
5795 //
5796 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5797 // true_scale_display. That does not make sense. The chart scale should be
5798 // the same as the true scale within the limits of the rounding factor.
5799 double true_scale_display =
5800 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5801 wxString text;
5802
5803 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5804
5805 if (m_displayed_scale_factor > 10.0)
5806 text.Printf(_T("%s %4.0f (%1.0fx)"), _("Scale"), true_scale_display,
5807 m_displayed_scale_factor);
5808 else if (m_displayed_scale_factor > 1.0)
5809 text.Printf(_T("%s %4.0f (%1.1fx)"), _("Scale"), true_scale_display,
5810 m_displayed_scale_factor);
5811 else if (m_displayed_scale_factor > 0.1) {
5812 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5813 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5814 } else if (m_displayed_scale_factor > 0.01) {
5815 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5816 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5817 } else {
5818 text.Printf(
5819 _T("%s %4.0f (---)"), _("Scale"),
5820 true_scale_display); // Generally, no chart, so no chart scale factor
5821 }
5822
5823 m_scaleValue = true_scale_display;
5824 m_scaleText = text;
5825 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5826
5827 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5828 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5829 // Check to see if the text will fit in the StatusBar field...
5830 bool b_noshow = false;
5831 {
5832 int w = 0;
5833 int h;
5834 wxClientDC dc(parent_frame->GetStatusBar());
5835 if (dc.IsOk()) {
5836 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5837 dc.SetFont(*templateFont);
5838 dc.GetTextExtent(text, &w, &h);
5839
5840 // If text is too long for the allocated field, try to reduce the text
5841 // string a bit.
5842 wxRect rect;
5843 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5844 if (w && w > rect.width) {
5845 text.Printf(_T("%s (%1.1fx)"), _("Scale"),
5846 m_displayed_scale_factor);
5847 }
5848
5849 // Test again...if too big still, then give it up.
5850 dc.GetTextExtent(text, &w, &h);
5851
5852 if (w && w > rect.width) {
5853 b_noshow = true;
5854 }
5855 }
5856 }
5857
5858 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5859 }
5860 }
5861
5862 // Maintain member vLat/vLon
5863 m_vLat = VPoint.clat;
5864 m_vLon = VPoint.clon;
5865
5866 return b_ret;
5867}
5868
5869// Static Icon definitions for some symbols requiring
5870// scaling/rotation/translation Very specific wxDC draw commands are
5871// necessary to properly render these icons...See the code in
5872// ShipDraw()
5873
5874// This icon was adapted and scaled from the S52 Presentation Library
5875// version 3_03.
5876// Symbol VECGND02
5877
5878static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5879
5880// This ownship icon was adapted and scaled from the S52 Presentation
5881// Library version 3_03 Symbol OWNSHP05
5882static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5883 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5884
5885wxColour ChartCanvas::PredColor() {
5886 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5887 // visibility.
5888 if (SHIP_NORMAL == m_ownship_state)
5889 return GetGlobalColor(_T ( "URED" ));
5890
5891 else if (SHIP_LOWACCURACY == m_ownship_state)
5892 return GetGlobalColor(_T ( "YELO1" ));
5893
5894 return GetGlobalColor(_T ( "NODTA" ));
5895}
5896
5897wxColour ChartCanvas::ShipColor() {
5898 // Establish ship color
5899 // It changes color based on GPS and Chart accuracy/availability
5900
5901 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor(_T ( "GREY1" ));
5902
5903 if (SHIP_LOWACCURACY == m_ownship_state)
5904 return GetGlobalColor(_T ( "YELO1" ));
5905
5906 return GetGlobalColor(_T ( "URED" )); // default is OK
5907}
5908
5909void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5910 wxPoint2DDouble lShipMidPoint) {
5911 dc.SetPen(wxPen(PredColor(), 2));
5912
5913 if (SHIP_NORMAL == m_ownship_state)
5914 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5915 else
5916 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "YELO1" ))));
5917
5918 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5919 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5920
5921 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5922 lShipMidPoint.m_y);
5923 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5924 lShipMidPoint.m_y + 12);
5925}
5926
5927void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5928 wxPoint GPSOffsetPixels,
5929 wxPoint2DDouble lGPSPoint) {
5930 if (m_animationActive) return;
5931 // Develop a uniform length for course predictor line dash length, based on
5932 // physical display size Use this reference length to size all other graphics
5933 // elements
5934 float ref_dim = m_display_size_mm / 24;
5935 ref_dim = wxMin(ref_dim, 12);
5936 ref_dim = wxMax(ref_dim, 6);
5937
5938 wxColour cPred;
5939 cPred.Set(g_cog_predictor_color);
5940 if (cPred == wxNullColour) cPred = PredColor();
5941
5942 // Establish some graphic element line widths dependent on the platform
5943 // display resolution
5944 // double nominal_line_width_pix = wxMax(1.0,
5945 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5946 // not less than 1 pixel
5947 double nominal_line_width_pix = wxMax(
5948 1.0,
5949 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5950
5951 // If the calculated value is greater than the config file spec value, then
5952 // use it.
5953 if (nominal_line_width_pix > g_cog_predictor_width)
5954 g_cog_predictor_width = nominal_line_width_pix;
5955
5956 // Calculate ownship Position Predictor
5957 wxPoint lPredPoint, lHeadPoint;
5958
5959 float pCog = std::isnan(gCog) ? 0 : gCog;
5960 float pSog = std::isnan(gSog) ? 0 : gSog;
5961
5962 double pred_lat, pred_lon;
5963 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5964 &pred_lat, &pred_lon);
5965 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5966
5967 // test to catch the case where COG/HDG line crosses the screen
5968 LLBBox box;
5969
5970 // Should we draw the Head vector?
5971 // Compare the points lHeadPoint and lPredPoint
5972 // If they differ by more than n pixels, and the head vector is valid, then
5973 // render the head vector
5974
5975 float ndelta_pix = 10.;
5976 double hdg_pred_lat, hdg_pred_lon;
5977 bool b_render_hdt = false;
5978 if (!std::isnan(gHdt)) {
5979 // Calculate ownship Heading pointer as a predictor
5980 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5981 &hdg_pred_lon);
5982 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5983 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5984 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5985 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5986 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5987 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5988 }
5989 }
5990
5991 // draw course over ground if they are longer than the ship
5992 wxPoint lShipMidPoint;
5993 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5994 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5995 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5996 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5997
5998 if (lpp >= img_height / 2) {
5999 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
6000 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
6001 !std::isnan(gSog)) {
6002 // COG Predictor
6003 float dash_length = ref_dim;
6004 wxDash dash_long[2];
6005 dash_long[0] =
6006 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6007 g_cog_predictor_width); // Long dash , in mm <---------+
6008 dash_long[1] = dash_long[0] / 2.0; // Short gap
6009
6010 // On ultra-hi-res displays, do not allow the dashes to be greater than
6011 // 250, since it is defined as (char)
6012 if (dash_length > 250.) {
6013 dash_long[0] = 250. / g_cog_predictor_width;
6014 dash_long[1] = dash_long[0] / 2;
6015 }
6016
6017 wxPen ppPen2(cPred, g_cog_predictor_width,
6018 (wxPenStyle)g_cog_predictor_style);
6019 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6020 ppPen2.SetDashes(2, dash_long);
6021 dc.SetPen(ppPen2);
6022 dc.StrokeLine(
6023 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6024 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6025
6026 if (g_cog_predictor_width > 1) {
6027 float line_width = g_cog_predictor_width / 3.;
6028
6029 wxDash dash_long3[2];
6030 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6031 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6032
6033 wxPen ppPen3(GetGlobalColor(_T ( "UBLCK" )), wxMax(1, line_width),
6034 (wxPenStyle)g_cog_predictor_style);
6035 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6036 ppPen3.SetDashes(2, dash_long3);
6037 dc.SetPen(ppPen3);
6038 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6039 lGPSPoint.m_y + GPSOffsetPixels.y,
6040 lPredPoint.x + GPSOffsetPixels.x,
6041 lPredPoint.y + GPSOffsetPixels.y);
6042 }
6043
6044 if (g_cog_predictor_endmarker) {
6045 // Prepare COG predictor endpoint icon
6046 double png_pred_icon_scale_factor = .4;
6047 if (g_ShipScaleFactorExp > 1.0)
6048 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6049 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6050
6051 wxPoint icon[4];
6052
6053 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6054 (float)(lPredPoint.x - lShipMidPoint.x));
6055 cog_rad += (float)PI;
6056
6057 for (int i = 0; i < 4; i++) {
6058 int j = i * 2;
6059 double pxa = (double)(s_png_pred_icon[j]);
6060 double pya = (double)(s_png_pred_icon[j + 1]);
6061
6062 pya *= png_pred_icon_scale_factor;
6063 pxa *= png_pred_icon_scale_factor;
6064
6065 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6066 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6067
6068 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6069 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6070 }
6071
6072 // Render COG endpoint icon
6073 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), g_cog_predictor_width / 2,
6074 wxPENSTYLE_SOLID);
6075 dc.SetPen(ppPen1);
6076 dc.SetBrush(wxBrush(cPred));
6077
6078 dc.StrokePolygon(4, icon);
6079 }
6080 }
6081 }
6082
6083 // HDT Predictor
6084 if (b_render_hdt) {
6085 float hdt_dash_length = ref_dim * 0.4;
6086
6087 cPred.Set(g_ownship_HDTpredictor_color);
6088 if (cPred == wxNullColour) cPred = PredColor();
6089 float hdt_width =
6090 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6091 : g_cog_predictor_width * 0.8);
6092 wxDash dash_short[2];
6093 dash_short[0] =
6094 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6095 hdt_width); // Short dash , in mm <---------+
6096 dash_short[1] =
6097 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6098 hdt_width); // Short gap |
6099
6100 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6101 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6102 ppPen2.SetDashes(2, dash_short);
6103
6104 dc.SetPen(ppPen2);
6105 dc.StrokeLine(
6106 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6107 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6108
6109 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6110 dc.SetPen(ppPen1);
6111 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "GREY2" ))));
6112
6113 if (g_ownship_HDTpredictor_endmarker) {
6114 double nominal_circle_size_pixels = wxMax(
6115 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6116
6117 // Scale the circle to ChartScaleFactor, slightly softened....
6118 if (g_ShipScaleFactorExp > 1.0)
6119 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6120
6121 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6122 lHeadPoint.y + GPSOffsetPixels.y,
6123 nominal_circle_size_pixels / 2);
6124 }
6125 }
6126
6127 // Draw radar rings if activated
6128 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6129 double factor = 1.00;
6130 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6131 factor = 1 / 1.852;
6132 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6133 if (std::isnan(gSog))
6134 factor = 0.0;
6135 else
6136 factor = gSog / 60;
6137 }
6138 factor *= g_fNavAidRadarRingsStep;
6139
6140 double tlat, tlon;
6141 wxPoint r;
6142 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6143 GetCanvasPointPix(tlat, tlon, &r);
6144
6145 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6146 pow((double)(lGPSPoint.m_y - r.y), 2));
6147 int pix_radius = (int)lpp;
6148
6149 extern wxColor GetDimColor(wxColor c);
6150 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6151
6152 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6153
6154 dc.SetPen(ppPen1);
6155 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6156
6157 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6158 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6159 }
6160}
6161
6162void ChartCanvas::ComputeShipScaleFactor(
6163 float icon_hdt, int ownShipWidth, int ownShipLength,
6164 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6165 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6166 float screenResolution = m_pix_per_mm;
6167
6168 // Calculate the true ship length in exact pixels
6169 double ship_bow_lat, ship_bow_lon;
6170 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6171 &ship_bow_lat, &ship_bow_lon);
6172 wxPoint lShipBowPoint;
6173 wxPoint2DDouble b_point =
6174 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6175 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6176
6177 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6178 powf((float)(b_point.m_y - a_point.m_y), 2));
6179
6180 // And in mm
6181 float shipLength_mm = shipLength_px / screenResolution;
6182
6183 // Set minimum ownship drawing size
6184 float ownship_min_mm = g_n_ownship_min_mm;
6185 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6186
6187 // Calculate Nautical Miles distance from midships to gps antenna
6188 float hdt_ant = icon_hdt + 180.;
6189 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6190 float dx = g_n_gps_antenna_offset_x / 1852.;
6191 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6192 {
6193 hdt_ant = icon_hdt;
6194 dy = -dy;
6195 }
6196
6197 // If the drawn ship size is going to be clamped, adjust the gps antenna
6198 // offsets
6199 if (shipLength_mm < ownship_min_mm) {
6200 dy /= shipLength_mm / ownship_min_mm;
6201 dx /= shipLength_mm / ownship_min_mm;
6202 }
6203
6204 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6205
6206 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6207 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6208 &ship_mid_lon1);
6209
6210 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6211 &lShipMidPoint);
6212
6213 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6214 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6215
6216 float scale_factor = shipLength_px / ownShipLength;
6217
6218 // Calculate a scale factor that would produce a reasonably sized icon
6219 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6220
6221 // And choose the correct one
6222 scale_factor = wxMax(scale_factor, scale_factor_min);
6223
6224 scale_factor_y = scale_factor;
6225 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6226 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6227}
6228
6229void ChartCanvas::ShipDraw(ocpnDC &dc) {
6230 if (!GetVP().IsValid()) return;
6231
6232 wxPoint GPSOffsetPixels(0, 0);
6233 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6234
6235 // COG/SOG may be undefined in NMEA data stream
6236 float pCog = std::isnan(gCog) ? 0 : gCog;
6237 float pSog = std::isnan(gSog) ? 0 : gSog;
6238
6239 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6240
6241 lShipMidPoint = lGPSPoint;
6242
6243 // Draw the icon rotated to the COG
6244 // or to the Hdt if available
6245 float icon_hdt = pCog;
6246 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6247
6248 // COG may be undefined in NMEA data stream
6249 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6250
6251 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6252 // predictor
6253 double osd_head_lat, osd_head_lon;
6254 wxPoint osd_head_point;
6255
6256 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6257 &osd_head_lon);
6258
6259 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6260
6261 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6262 (float)(osd_head_point.x - lShipMidPoint.m_x));
6263 icon_rad += (float)PI;
6264
6265 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6266
6267 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6268 // nominal size and is just barely outside the viewport ....
6269 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6270
6271 // TODO: fix to include actual size of boat that will be rendered
6272 int img_height = 0;
6273 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6274 if (GetVP().chart_scale >
6275 300000) // According to S52, this should be 50,000
6276 {
6277 ShipDrawLargeScale(dc, lShipMidPoint);
6278 img_height = 20;
6279 } else {
6280 wxImage pos_image;
6281
6282 // Substitute user ownship image if found
6283 if (m_pos_image_user)
6284 pos_image = m_pos_image_user->Copy();
6285 else if (SHIP_NORMAL == m_ownship_state)
6286 pos_image = m_pos_image_red->Copy();
6287 if (SHIP_LOWACCURACY == m_ownship_state)
6288 pos_image = m_pos_image_yellow->Copy();
6289 else if (SHIP_NORMAL != m_ownship_state)
6290 pos_image = m_pos_image_grey->Copy();
6291
6292 // Substitute user ownship image if found
6293 if (m_pos_image_user) {
6294 pos_image = m_pos_image_user->Copy();
6295
6296 if (SHIP_LOWACCURACY == m_ownship_state)
6297 pos_image = m_pos_image_user_yellow->Copy();
6298 else if (SHIP_NORMAL != m_ownship_state)
6299 pos_image = m_pos_image_user_grey->Copy();
6300 }
6301
6302 img_height = pos_image.GetHeight();
6303
6304 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6305 g_OwnShipIconType > 0) // use large ship
6306 {
6307 int ownShipWidth = 22; // Default values from s_ownship_icon
6308 int ownShipLength = 84;
6309 if (g_OwnShipIconType == 1) {
6310 ownShipWidth = pos_image.GetWidth();
6311 ownShipLength = pos_image.GetHeight();
6312 }
6313
6314 float scale_factor_x, scale_factor_y;
6315 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6316 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6317 scale_factor_x, scale_factor_y);
6318
6319 if (g_OwnShipIconType == 1) { // Scaled bitmap
6320 pos_image.Rescale(ownShipWidth * scale_factor_x,
6321 ownShipLength * scale_factor_y,
6322 wxIMAGE_QUALITY_HIGH);
6323 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6324 wxImage rot_image =
6325 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6326
6327 // Simple sharpening algorithm.....
6328 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6329 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6330 if (rot_image.GetAlpha(ip, jp) > 64)
6331 rot_image.SetAlpha(ip, jp, 255);
6332
6333 wxBitmap os_bm(rot_image);
6334
6335 int w = os_bm.GetWidth();
6336 int h = os_bm.GetHeight();
6337 img_height = h;
6338
6339 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6340 lShipMidPoint.m_y - h / 2, true);
6341
6342 // Maintain dirty box,, missing in __WXMSW__ library
6343 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6344 lShipMidPoint.m_y - h / 2);
6345 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6346 lShipMidPoint.m_y - h / 2 + h);
6347 }
6348
6349 else if (g_OwnShipIconType == 2) { // Scaled Vector
6350 wxPoint ownship_icon[10];
6351
6352 for (int i = 0; i < 10; i++) {
6353 int j = i * 2;
6354 float pxa = (float)(s_ownship_icon[j]);
6355 float pya = (float)(s_ownship_icon[j + 1]);
6356 pya *= scale_factor_y;
6357 pxa *= scale_factor_x;
6358
6359 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6360 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6361
6362 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6363 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6364 }
6365
6366 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), 1, wxPENSTYLE_SOLID);
6367 dc.SetPen(ppPen1);
6368 dc.SetBrush(wxBrush(ShipColor()));
6369
6370 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6371
6372 // draw reference point (midships) cross
6373 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6374 ownship_icon[7].y);
6375 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6376 ownship_icon[9].y);
6377 }
6378
6379 img_height = ownShipLength * scale_factor_y;
6380
6381 // Reference point, where the GPS antenna is
6382 int circle_rad = 3;
6383 if (m_pos_image_user) circle_rad = 1;
6384
6385 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6386 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6387 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6388 } else { // Fixed bitmap icon.
6389 /* non opengl, or suboptimal opengl via ocpndc: */
6390 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6391 wxImage rot_image =
6392 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6393
6394 // Simple sharpening algorithm.....
6395 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6396 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6397 if (rot_image.GetAlpha(ip, jp) > 64)
6398 rot_image.SetAlpha(ip, jp, 255);
6399
6400 wxBitmap os_bm(rot_image);
6401
6402 if (g_ShipScaleFactorExp > 1) {
6403 wxImage scaled_image = os_bm.ConvertToImage();
6404 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6405 1.0; // soften the scale factor a bit
6406 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6407 scaled_image.GetHeight() * factor,
6408 wxIMAGE_QUALITY_HIGH));
6409 }
6410 int w = os_bm.GetWidth();
6411 int h = os_bm.GetHeight();
6412 img_height = h;
6413
6414 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6415 lShipMidPoint.m_y - h / 2, true);
6416
6417 // Reference point, where the GPS antenna is
6418 int circle_rad = 3;
6419 if (m_pos_image_user) circle_rad = 1;
6420
6421 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6422 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6423 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6424
6425 // Maintain dirty box,, missing in __WXMSW__ library
6426 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6427 lShipMidPoint.m_y - h / 2);
6428 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6429 lShipMidPoint.m_y - h / 2 + h);
6430 }
6431 } // ownship draw
6432 }
6433
6434 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6435}
6436
6437/* @ChartCanvas::CalcGridSpacing
6438 **
6439 ** Calculate the major and minor spacing between the lat/lon grid
6440 **
6441 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6442 *window
6443 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6444 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6445 ** @return [void]
6446 */
6447void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6448 float &MinorSpacing) {
6449 // table for calculating the distance between the grids
6450 // [0] view_scale ppm
6451 // [1] spacing between major grid lines in degrees
6452 // [2] spacing between minor grid lines in degrees
6453 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6454 {.000001f, 45.0f, 15.0f},
6455 {.0002f, 30.0f, 10.0f},
6456 {.0003f, 10.0f, 2.0f},
6457 {.0008f, 5.0f, 1.0f},
6458 {.001f, 2.0f, 30.0f / 60.0f},
6459 {.003f, 1.0f, 20.0f / 60.0f},
6460 {.006f, 0.5f, 10.0f / 60.0f},
6461 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6462 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6463 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6464 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6465 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6466 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6467 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6468 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6469
6470 unsigned int tabi;
6471 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6472 if (view_scale_ppm < lltab[tabi][0]) break;
6473 MajorSpacing = lltab[tabi][1]; // major latitude distance
6474 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6475 return;
6476}
6477/* @ChartCanvas::CalcGridText *************************************
6478 **
6479 ** Calculates text to display at the major grid lines
6480 **
6481 ** @param [r] latlon [float] latitude or longitude of grid line
6482 ** @param [r] spacing [float] distance between two major grid lines
6483 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6484 **
6485 ** @return
6486 */
6487
6488wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6489 int deg = (int)fabs(latlon); // degrees
6490 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6491 char postfix;
6492
6493 // calculate postfix letter (NSEW)
6494 if (latlon > 0.0) {
6495 if (bPostfix) {
6496 postfix = 'N';
6497 } else {
6498 postfix = 'E';
6499 }
6500 } else if (latlon < 0.0) {
6501 if (bPostfix) {
6502 postfix = 'S';
6503 } else {
6504 postfix = 'W';
6505 }
6506 } else {
6507 postfix = ' '; // no postfix for equator and greenwich
6508 }
6509 // calculate text, display minutes only if spacing is smaller than one degree
6510
6511 wxString ret;
6512 if (spacing >= 1.0) {
6513 ret.Printf(_T("%3d%c %c"), deg, 0x00b0, postfix);
6514 } else if (spacing >= (1.0 / 60.0)) {
6515 ret.Printf(_T("%3d%c%02.0f %c"), deg, 0x00b0, min, postfix);
6516 } else {
6517 ret.Printf(_T("%3d%c%02.2f %c"), deg, 0x00b0, min, postfix);
6518 }
6519
6520 return ret;
6521}
6522
6523/* @ChartCanvas::GridDraw *****************************************
6524 **
6525 ** Draws major and minor Lat/Lon Grid on the chart
6526 ** - distance between Grid-lm ines are calculated automatic
6527 ** - major grid lines will be across the whole chart window
6528 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6529 **
6530 ** @param [w] dc [wxDC&] the wx drawing context
6531 **
6532 ** @return [void]
6533 ************************************************************************/
6534void ChartCanvas::GridDraw(ocpnDC &dc) {
6535 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6536
6537 double nlat, elon, slat, wlon;
6538 float lat, lon;
6539 float dlon;
6540 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6541 wxCoord w, h;
6542 wxPen GridPen(GetGlobalColor(_T ( "SNDG1" )), 1, wxPENSTYLE_SOLID);
6543 dc.SetPen(GridPen);
6544 if (!m_pgridFont) SetupGridFont();
6545 dc.SetFont(*m_pgridFont);
6546 dc.SetTextForeground(GetGlobalColor(_T ( "SNDG1" )));
6547
6548 w = m_canvas_width;
6549 h = m_canvas_height;
6550
6551 GetCanvasPixPoint(0, 0, nlat,
6552 wlon); // get lat/lon of upper left point of the window
6553 GetCanvasPixPoint(w, h, slat,
6554 elon); // get lat/lon of lower right point of the window
6555 dlon =
6556 elon -
6557 wlon; // calculate how many degrees of longitude are shown in the window
6558 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6559 {
6560 dlon = dlon + 360.0;
6561 }
6562 // calculate distance between latitude grid lines
6563 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6564
6565 // calculate position of first major latitude grid line
6566 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6567
6568 // Draw Major latitude grid lines and text
6569 while (lat < nlat) {
6570 wxPoint r;
6571 wxString st =
6572 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6573 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6574 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6575 dc.DrawText(st, 0, r.y); // draw text
6576 lat = lat + gridlatMajor;
6577
6578 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6579 }
6580
6581 // calculate position of first minor latitude grid line
6582 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6583
6584 // Draw minor latitude grid lines
6585 while (lat < nlat) {
6586 wxPoint r;
6587 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6588 dc.DrawLine(0, r.y, 10, r.y, false);
6589 dc.DrawLine(w - 10, r.y, w, r.y, false);
6590 lat = lat + gridlatMinor;
6591 }
6592
6593 // calculate distance between grid lines
6594 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6595
6596 // calculate position of first major latitude grid line
6597 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6598
6599 // draw major longitude grid lines
6600 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6601 wxPoint r;
6602 wxString st = CalcGridText(lon, gridlonMajor, false);
6603 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6604 dc.DrawLine(r.x, 0, r.x, h, false);
6605 dc.DrawText(st, r.x, 0);
6606 lon = lon + gridlonMajor;
6607 if (lon > 180.0) {
6608 lon = lon - 360.0;
6609 }
6610
6611 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6612 }
6613
6614 // calculate position of first minor longitude grid line
6615 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6616 // draw minor longitude grid lines
6617 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6618 wxPoint r;
6619 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6620 dc.DrawLine(r.x, 0, r.x, 10, false);
6621 dc.DrawLine(r.x, h - 10, r.x, h, false);
6622 lon = lon + gridlonMinor;
6623 if (lon > 180.0) {
6624 lon = lon - 360.0;
6625 }
6626 }
6627}
6628
6629void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6630 if (0 ) {
6631 double blat, blon, tlat, tlon;
6632 wxPoint r;
6633
6634 int x_origin = m_bDisplayGrid ? 60 : 20;
6635 int y_origin = m_canvas_height - 50;
6636
6637 float dist;
6638 int count;
6639 wxPen pen1, pen2;
6640
6641 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6642 {
6643 dist = 10.0;
6644 count = 5;
6645 pen1 = wxPen(GetGlobalColor(_T ( "SNDG2" )), 3, wxPENSTYLE_SOLID);
6646 pen2 = wxPen(GetGlobalColor(_T ( "SNDG1" )), 3, wxPENSTYLE_SOLID);
6647 } else // Draw 1 mile scale as SCALEB10
6648 {
6649 dist = 1.0;
6650 count = 10;
6651 pen1 = wxPen(GetGlobalColor(_T ( "SCLBR" )), 3, wxPENSTYLE_SOLID);
6652 pen2 = wxPen(GetGlobalColor(_T ( "CHGRD" )), 3, wxPENSTYLE_SOLID);
6653 }
6654
6655 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6656 double rotation = -VPoint.rotation;
6657
6658 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6659 GetCanvasPointPix(tlat, tlon, &r);
6660 int l1 = (y_origin - r.y) / count;
6661
6662 for (int i = 0; i < count; i++) {
6663 int y = l1 * i;
6664 if (i & 1)
6665 dc.SetPen(pen1);
6666 else
6667 dc.SetPen(pen2);
6668
6669 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6670 }
6671 } else {
6672 double blat, blon, tlat, tlon;
6673
6674 int x_origin = 5.0 * GetPixPerMM();
6675 int chartbar_height = GetChartbarHeight();
6676 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6677 // if (style->chartStatusWindowTransparent)
6678 // chartbar_height = 0;
6679 int y_origin = m_canvas_height - chartbar_height - 5;
6680#ifdef __WXOSX__
6681 if (!g_bopengl)
6682 y_origin =
6683 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6684#endif
6685
6686 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6687 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6688
6689 double d;
6690 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6691 d /= 2;
6692
6693 int unit = g_iDistanceFormat;
6694 if (d < .5 &&
6695 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6696 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6697
6698 // nice number
6699 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6700 float places = floor(logdist), rem = logdist - places;
6701 dist = pow(10, places);
6702
6703 if (rem < .2)
6704 dist /= 5;
6705 else if (rem < .5)
6706 dist /= 2;
6707
6708 wxString s = wxString::Format(_T("%g "), dist) + getUsrDistanceUnit(unit);
6709 wxPen pen1 = wxPen(GetGlobalColor(_T ( "UBLCK" )), 3, wxPENSTYLE_SOLID);
6710 double rotation = -VPoint.rotation;
6711
6712 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6713 &tlat, &tlon);
6714 wxPoint r;
6715 GetCanvasPointPix(tlat, tlon, &r);
6716 int l1 = r.x - x_origin;
6717
6718 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6719 12); // Store this for later reference
6720
6721 dc.SetPen(pen1);
6722
6723 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6724 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6725 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6726
6727 if (!m_pgridFont) SetupGridFont();
6728 dc.SetFont(*m_pgridFont);
6729 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
6730 int w, h;
6731 dc.GetTextExtent(s, &w, &h);
6732 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6733 if (g_bopengl) {
6734 w /= dpi_factor;
6735 h /= dpi_factor;
6736 }
6737 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6738 }
6739}
6740
6741void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6742 // Constants?
6743 double da_min = 2.;
6744 double da_max = 6.;
6745 double ra_min = 0.;
6746 double ra_max = 40.;
6747
6748 wxPen pen_save = dc.GetPen();
6749
6750 wxDateTime now = wxDateTime::Now();
6751
6752 dc.SetPen(pen);
6753
6754 int x0, y0, x1, y1;
6755
6756 x0 = x1 = x + radius; // Start point
6757 y0 = y1 = y;
6758 double angle = 0.;
6759 int i = 0;
6760
6761 while (angle < 360.) {
6762 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6763 angle += da;
6764
6765 if (angle > 360.) angle = 360.;
6766
6767 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6768
6769 double r;
6770 if (i & 1)
6771 r = radius + ra;
6772 else
6773 r = radius - ra;
6774
6775 x1 = (int)(x + cos(angle * PI / 180.) * r);
6776 y1 = (int)(y + sin(angle * PI / 180.) * r);
6777
6778 dc.DrawLine(x0, y0, x1, y1);
6779
6780 x0 = x1;
6781 y0 = y1;
6782
6783 i++;
6784 }
6785
6786 dc.DrawLine(x + radius, y, x1, y1); // closure
6787
6788 dc.SetPen(pen_save);
6789}
6790
6791static bool bAnchorSoundPlaying = false;
6792
6793static void onAnchorSoundFinished(void *ptr) {
6794 g_anchorwatch_sound->UnLoad();
6795 bAnchorSoundPlaying = false;
6796}
6797
6798void ChartCanvas::AlertDraw(ocpnDC &dc) {
6799 // Visual and audio alert for anchorwatch goes here
6800 bool play_sound = false;
6801 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6802 if (AnchorAlertOn1) {
6803 wxPoint TargetPoint;
6804 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
6805 &TargetPoint);
6806 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6807 TargetPoint.y, 100);
6808 play_sound = true;
6809 }
6810 } else
6811 AnchorAlertOn1 = false;
6812
6813 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6814 if (AnchorAlertOn2) {
6815 wxPoint TargetPoint;
6816 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
6817 &TargetPoint);
6818 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6819 TargetPoint.y, 100);
6820 play_sound = true;
6821 }
6822 } else
6823 AnchorAlertOn2 = false;
6824
6825 if (play_sound) {
6826 if (!bAnchorSoundPlaying) {
6827 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6828 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6829 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6830 if (g_anchorwatch_sound->IsOk()) {
6831 bAnchorSoundPlaying = true;
6832 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6833 g_anchorwatch_sound->Play();
6834 }
6835 }
6836 }
6837}
6838
6839void ChartCanvas::UpdateShips() {
6840 // Get the rectangle in the current dc which bounds the "ownship" symbol
6841
6842 wxClientDC dc(this);
6843 if (!dc.IsOk()) return;
6844
6845 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6846 if (!test_bitmap.IsOk()) return;
6847
6848 wxMemoryDC temp_dc(test_bitmap);
6849
6850 temp_dc.ResetBoundingBox();
6851 temp_dc.DestroyClippingRegion();
6852 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6853
6854 // Draw the ownship on the temp_dc
6855 ocpnDC ocpndc = ocpnDC(temp_dc);
6856 ShipDraw(ocpndc);
6857
6858 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6859 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6860 if (p) {
6861 wxPoint px;
6862 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6863 ocpndc.CalcBoundingBox(px.x, px.y);
6864 }
6865 }
6866
6867 ship_draw_rect =
6868 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6869 temp_dc.MaxY() - temp_dc.MinY());
6870
6871 wxRect own_ship_update_rect = ship_draw_rect;
6872
6873 if (!own_ship_update_rect.IsEmpty()) {
6874 // The required invalidate rectangle is the union of the last drawn
6875 // rectangle and this drawn rectangle
6876 own_ship_update_rect.Union(ship_draw_last_rect);
6877 own_ship_update_rect.Inflate(2);
6878 }
6879
6880 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6881
6882 ship_draw_last_rect = ship_draw_rect;
6883
6884 temp_dc.SelectObject(wxNullBitmap);
6885}
6886
6887void ChartCanvas::UpdateAlerts() {
6888 // Get the rectangle in the current dc which bounds the detected Alert
6889 // targets
6890
6891 // Use this dc
6892 wxClientDC dc(this);
6893
6894 // Get dc boundary
6895 int sx, sy;
6896 dc.GetSize(&sx, &sy);
6897
6898 // Need a bitmap
6899 wxBitmap test_bitmap(sx, sy, -1);
6900
6901 // Create a memory DC
6902 wxMemoryDC temp_dc;
6903 temp_dc.SelectObject(test_bitmap);
6904
6905 temp_dc.ResetBoundingBox();
6906 temp_dc.DestroyClippingRegion();
6907 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6908
6909 // Draw the Alert Targets on the temp_dc
6910 ocpnDC ocpndc = ocpnDC(temp_dc);
6911 AlertDraw(ocpndc);
6912
6913 // Retrieve the drawing extents
6914 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6915 temp_dc.MaxX() - temp_dc.MinX(),
6916 temp_dc.MaxY() - temp_dc.MinY());
6917
6918 if (!alert_rect.IsEmpty())
6919 alert_rect.Inflate(2); // clear all drawing artifacts
6920
6921 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6922 // The required invalidate rectangle is the union of the last drawn
6923 // rectangle and this drawn rectangle
6924 wxRect alert_update_rect = alert_draw_rect;
6925 alert_update_rect.Union(alert_rect);
6926
6927 // Invalidate the rectangular region
6928 RefreshRect(alert_update_rect, false);
6929 }
6930
6931 // Save this rectangle for next time
6932 alert_draw_rect = alert_rect;
6933
6934 temp_dc.SelectObject(wxNullBitmap); // clean up
6935}
6936
6937void ChartCanvas::UpdateAIS() {
6938 if (!g_pAIS) return;
6939
6940 // Get the rectangle in the current dc which bounds the detected AIS targets
6941
6942 // Use this dc
6943 wxClientDC dc(this);
6944
6945 // Get dc boundary
6946 int sx, sy;
6947 dc.GetSize(&sx, &sy);
6948
6949 wxRect ais_rect;
6950
6951 // How many targets are there?
6952
6953 // If more than "some number", it will be cheaper to refresh the entire
6954 // screen than to build update rectangles for each target.
6955 if (g_pAIS->GetTargetList().size() > 10) {
6956 ais_rect = wxRect(0, 0, sx, sy); // full screen
6957 } else {
6958 // Need a bitmap
6959 wxBitmap test_bitmap(sx, sy, -1);
6960
6961 // Create a memory DC
6962 wxMemoryDC temp_dc;
6963 temp_dc.SelectObject(test_bitmap);
6964
6965 temp_dc.ResetBoundingBox();
6966 temp_dc.DestroyClippingRegion();
6967 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6968
6969 // Draw the AIS Targets on the temp_dc
6970 ocpnDC ocpndc = ocpnDC(temp_dc);
6971 AISDraw(ocpndc, GetVP(), this);
6972 AISDrawAreaNotices(ocpndc, GetVP(), this);
6973
6974 // Retrieve the drawing extents
6975 ais_rect =
6976 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6977 temp_dc.MaxY() - temp_dc.MinY());
6978
6979 if (!ais_rect.IsEmpty())
6980 ais_rect.Inflate(2); // clear all drawing artifacts
6981
6982 temp_dc.SelectObject(wxNullBitmap); // clean up
6983 }
6984
6985 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6986 // The required invalidate rectangle is the union of the last drawn
6987 // rectangle and this drawn rectangle
6988 wxRect ais_update_rect = ais_draw_rect;
6989 ais_update_rect.Union(ais_rect);
6990
6991 // Invalidate the rectangular region
6992 RefreshRect(ais_update_rect, false);
6993 }
6994
6995 // Save this rectangle for next time
6996 ais_draw_rect = ais_rect;
6997}
6998
6999void ChartCanvas::ToggleCPAWarn() {
7000 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
7001 wxString mess;
7002 if (g_bCPAWarn) {
7003 g_bTCPA_Max = true;
7004 mess = _("ON");
7005 } else {
7006 g_bTCPA_Max = false;
7007 mess = _("OFF");
7008 }
7009 // Print to status bar if available.
7010 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
7011 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7012 } else {
7013 if (!g_AisFirstTimeUse) {
7014 OCPNMessageBox(this,
7015 _("CPA Alarm is switched") + _T(" ") + mess.MakeLower(),
7016 _("CPA") + _T(" ") + mess, 4, 4);
7017 }
7018 }
7019}
7020
7021void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7022
7023void ChartCanvas::OnSize(wxSizeEvent &event) {
7024 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7025 // GetClientSize returns the size of the canvas area in logical pixels.
7026 GetClientSize(&m_canvas_width, &m_canvas_height);
7027
7028#ifdef __WXOSX__
7029 // Support scaled HDPI displays.
7030 m_displayScale = GetContentScaleFactor();
7031#endif
7032
7033 // Convert to physical pixels.
7034 m_canvas_width *= m_displayScale;
7035 m_canvas_height *= m_displayScale;
7036
7037 // Resize the current viewport
7038 VPoint.pix_width = m_canvas_width;
7039 VPoint.pix_height = m_canvas_height;
7040 VPoint.SetPixelScale(m_displayScale);
7041
7042 // Get some canvas metrics
7043
7044 // Rescale to current value, in order to rebuild VPoint data
7045 // structures for new canvas size
7047
7048 m_absolute_min_scale_ppm =
7049 m_canvas_width /
7050 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7051
7052 // Inform the parent Frame that I am being resized...
7053 gFrame->ProcessCanvasResize();
7054
7055 // if MUIBar is active, size the bar
7056 // if(g_useMUI && !m_muiBar){ // rebuild if
7057 // necessary
7058 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7059 // m_muiBarHOSize = m_muiBar->GetSize();
7060 // }
7061
7062 if (m_muiBar) {
7063 SetMUIBarPosition();
7064 UpdateFollowButtonState();
7065 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7066 }
7067
7068 // Set up the scroll margins
7069 xr_margin = m_canvas_width * 95 / 100;
7070 xl_margin = m_canvas_width * 5 / 100;
7071 yt_margin = m_canvas_height * 5 / 100;
7072 yb_margin = m_canvas_height * 95 / 100;
7073
7074 if (m_pQuilt)
7075 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7076
7077 // Resize the scratch BM
7078 delete pscratch_bm;
7079 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7080 m_brepaint_piano = true;
7081
7082 // Resize the Route Calculation BM
7083 m_dc_route.SelectObject(wxNullBitmap);
7084 delete proute_bm;
7085 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7086 m_dc_route.SelectObject(*proute_bm);
7087
7088 // Resize the saved Bitmap
7089 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7090
7091 // Resize the working Bitmap
7092 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7093
7094 // Rescale again, to capture all the changes for new canvas size
7096
7097#ifdef ocpnUSE_GL
7098 if (/*g_bopengl &&*/ m_glcc) {
7099 // FIXME (dave) This can go away?
7100 m_glcc->OnSize(event);
7101 }
7102#endif
7103
7104 FormatPianoKeys();
7105 // Invalidate the whole window
7106 ReloadVP();
7107}
7108
7109void ChartCanvas::ProcessNewGUIScale() {
7110 // m_muiBar->Hide();
7111 delete m_muiBar;
7112 m_muiBar = 0;
7113
7114 CreateMUIBar();
7115}
7116
7117void ChartCanvas::CreateMUIBar() {
7118 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7119
7120 // We need to update the m_bENCGroup flag, at least for the initial creation
7121 // of a MUIBar
7122 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7123
7124 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7125 m_muiBar->SetColorScheme(m_cs);
7126 m_muiBarHOSize = m_muiBar->m_size;
7127 }
7128
7129 if (m_muiBar) {
7130 SetMUIBarPosition();
7131 UpdateFollowButtonState();
7132 m_muiBar->UpdateDynamicValues();
7133 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7134 }
7135}
7136
7137void ChartCanvas::SetMUIBarPosition() {
7138 // if MUIBar is active, size the bar
7139 if (m_muiBar) {
7140 // We estimate the piano width based on the canvas width
7141 int pianoWidth = GetClientSize().x * 0.6f;
7142 // If the piano already exists, we can use its exact width
7143 // if(m_Piano)
7144 // pianoWidth = m_Piano->GetWidth();
7145
7146 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7147 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7148 delete m_muiBar;
7149 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7150 m_muiBar->SetColorScheme(m_cs);
7151 }
7152 }
7153
7154 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7155 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7156 delete m_muiBar;
7157 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7158 m_muiBar->SetColorScheme(m_cs);
7159 }
7160 }
7161
7162 m_muiBar->SetBestPosition();
7163 }
7164}
7165
7166void ChartCanvas::DestroyMuiBar() {
7167 if (m_muiBar) {
7168 delete m_muiBar;
7169 m_muiBar = NULL;
7170 }
7171}
7172
7173void ChartCanvas::ShowCompositeInfoWindow(
7174 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7175 if (n_charts > 0) {
7176 if (NULL == m_pCIWin) {
7177 m_pCIWin = new ChInfoWin(this);
7178 m_pCIWin->Hide();
7179 }
7180
7181 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7182 wxString s;
7183
7184 s = _("Composite of ");
7185
7186 wxString s1;
7187 s1.Printf("%d ", n_charts);
7188 if (n_charts > 1)
7189 s1 += _("charts");
7190 else
7191 s1 += _("chart");
7192 s += s1;
7193 s += '\n';
7194
7195 s1.Printf(_("Chart scale"));
7196 s1 += ": ";
7197 wxString s2;
7198 s2.Printf("1:%d\n", scale);
7199 s += s1;
7200 s += s2;
7201
7202 s1 = _("Zoom in for more information");
7203 s += s1;
7204 s += '\n';
7205
7206 int char_width = s1.Length();
7207 int char_height = 3;
7208
7209 if (g_bChartBarEx) {
7210 s += '\n';
7211 int j = 0;
7212 for (int i : index_vector) {
7213 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7214 wxString path = cte.GetFullSystemPath();
7215 s += path;
7216 s += '\n';
7217 char_height++;
7218 char_width = wxMax(char_width, path.Length());
7219 if (j++ >= 9) break;
7220 }
7221 if (j >= 9) {
7222 s += " .\n .\n .\n";
7223 char_height += 3;
7224 }
7225 s += '\n';
7226 char_height += 1;
7227
7228 char_width += 4; // Fluff
7229 }
7230
7231 m_pCIWin->SetString(s);
7232
7233 m_pCIWin->FitToChars(char_width, char_height);
7234
7235 wxPoint p;
7236 p.x = x / GetContentScaleFactor();
7237 if ((p.x + m_pCIWin->GetWinSize().x) >
7238 (m_canvas_width / GetContentScaleFactor()))
7239 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7240 m_pCIWin->GetWinSize().x) /
7241 2; // centered
7242
7243 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7244 4 - m_pCIWin->GetWinSize().y;
7245
7246 m_pCIWin->dbIndex = 0;
7247 m_pCIWin->chart_scale = 0;
7248 m_pCIWin->SetPosition(p);
7249 m_pCIWin->SetBitmap();
7250 m_pCIWin->Refresh();
7251 m_pCIWin->Show();
7252 }
7253 } else {
7254 HideChartInfoWindow();
7255 }
7256}
7257
7258void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7259 if (dbIndex >= 0) {
7260 if (NULL == m_pCIWin) {
7261 m_pCIWin = new ChInfoWin(this);
7262 m_pCIWin->Hide();
7263 }
7264
7265 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7266 wxString s;
7267 ChartBase *pc = NULL;
7268
7269 // TOCTOU race but worst case will reload chart.
7270 // need to lock it or the background spooler may evict charts in
7271 // OpenChartFromDBAndLock
7272 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7273 pc = ChartData->OpenChartFromDBAndLock(
7274 dbIndex, FULL_INIT); // this must come from cache
7275
7276 int char_width, char_height;
7277 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7278 if (pc) ChartData->UnLockCacheChart(dbIndex);
7279
7280 m_pCIWin->SetString(s);
7281 m_pCIWin->FitToChars(char_width, char_height);
7282
7283 wxPoint p;
7284 p.x = x / GetContentScaleFactor();
7285 if ((p.x + m_pCIWin->GetWinSize().x) >
7286 (m_canvas_width / GetContentScaleFactor()))
7287 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7288 m_pCIWin->GetWinSize().x) /
7289 2; // centered
7290
7291 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7292 4 - m_pCIWin->GetWinSize().y;
7293
7294 m_pCIWin->dbIndex = dbIndex;
7295 m_pCIWin->SetPosition(p);
7296 m_pCIWin->SetBitmap();
7297 m_pCIWin->Refresh();
7298 m_pCIWin->Show();
7299 }
7300 } else {
7301 HideChartInfoWindow();
7302 }
7303}
7304
7305void ChartCanvas::HideChartInfoWindow(void) {
7306 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7307 m_pCIWin->Hide();
7308 m_pCIWin->Destroy();
7309 m_pCIWin = NULL;
7310
7311#ifdef __ANDROID__
7312 androidForceFullRepaint();
7313#endif
7314 }
7315}
7316
7317void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7318 wxMouseEvent ev(wxEVT_MOTION);
7319 ev.m_x = mouse_x;
7320 ev.m_y = mouse_y;
7321 ev.m_leftDown = mouse_leftisdown;
7322
7323 wxEvtHandler *evthp = GetEventHandler();
7324
7325 ::wxPostEvent(evthp, ev);
7326}
7327
7328void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7329 if ((m_panx_target_final - m_panx_target_now) ||
7330 (m_pany_target_final - m_pany_target_now)) {
7331 DoTimedMovementTarget();
7332 } else
7333 DoTimedMovement();
7334}
7335
7336void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7337
7338bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7339 int delta) {
7340 if (m_disable_edge_pan) return false;
7341
7342 bool bft = false;
7343 int pan_margin = m_canvas_width * margin / 100;
7344 int pan_timer_set = 200;
7345 double pan_delta = GetVP().pix_width * delta / 100;
7346 int pan_x = 0;
7347 int pan_y = 0;
7348
7349 if (x > m_canvas_width - pan_margin) {
7350 bft = true;
7351 pan_x = pan_delta;
7352 }
7353
7354 else if (x < pan_margin) {
7355 bft = true;
7356 pan_x = -pan_delta;
7357 }
7358
7359 if (y < pan_margin) {
7360 bft = true;
7361 pan_y = -pan_delta;
7362 }
7363
7364 else if (y > m_canvas_height - pan_margin) {
7365 bft = true;
7366 pan_y = pan_delta;
7367 }
7368
7369 // Of course, if dragging, and the mouse left button is not down, we must
7370 // stop the event injection
7371 if (bdragging) {
7372 if (!g_btouch) {
7373 wxMouseState state = ::wxGetMouseState();
7374#if wxCHECK_VERSION(3, 0, 0)
7375 if (!state.LeftIsDown())
7376#else
7377 if (!state.LeftDown())
7378#endif
7379 bft = false;
7380 }
7381 }
7382 if ((bft) && !pPanTimer->IsRunning()) {
7383 PanCanvas(pan_x, pan_y);
7384 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7385 return true;
7386 }
7387
7388 // This mouse event must not be due to pan timer event injector
7389 // Mouse is out of the pan zone, so prevent any orphan event injection
7390 if ((!bft) && pPanTimer->IsRunning()) {
7391 pPanTimer->Stop();
7392 }
7393
7394 return (false);
7395}
7396
7397// Look for waypoints at the current position.
7398// Used to determine what a mouse event should act on.
7399
7400void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7401 bool setBeingEdited) {
7402 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7403 m_pRoutePointEditTarget = NULL;
7404 m_pFoundPoint = NULL;
7405
7406 SelectItem *pFind = NULL;
7407 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7408 SelectableItemList SelList = pSelect->FindSelectionList(
7409 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7410 wxSelectableItemListNode *node = SelList.GetFirst();
7411 while (node) {
7412 pFind = node->GetData();
7413
7414 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7415
7416 // Get an array of all routes using this point
7417 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7418 // TODO: delete m_pEditRouteArray after use?
7419
7420 // Use route array to determine actual visibility for the point
7421 bool brp_viz = false;
7422 if (m_pEditRouteArray) {
7423 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7424 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7425 if (pr->IsVisible()) {
7426 brp_viz = true;
7427 break;
7428 }
7429 }
7430 } else
7431 brp_viz = frp->IsVisible(); // isolated point
7432
7433 if (brp_viz) {
7434 // Use route array to rubberband all affected routes
7435 if (m_pEditRouteArray) // Editing Waypoint as part of route
7436 {
7437 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7438 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7439 pr->m_bIsBeingEdited = setBeingEdited;
7440 }
7441 m_bRouteEditing = setBeingEdited;
7442 } else // editing Mark
7443 {
7444 frp->m_bRPIsBeingEdited = setBeingEdited;
7445 m_bMarkEditing = setBeingEdited;
7446 }
7447
7448 m_pRoutePointEditTarget = frp;
7449 m_pFoundPoint = pFind;
7450 break; // out of the while(node)
7451 }
7452
7453 node = node->GetNext();
7454 } // while (node)
7455}
7456
7457void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7458 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7459 singleClickEventIsValid = false;
7460 m_DoubleClickTimer->Stop();
7461}
7462
7463bool leftIsDown;
7464
7465bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7466 if (!m_bChartDragging && !m_bDrawingRoute) {
7467 /*
7468 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7469 * mouse event coordinates are in logical pixels.
7470 */
7471 if (m_Compass && m_Compass->IsShown()) {
7472 wxRect logicalRect = m_Compass->GetLogicalRect();
7473 bool isInCompass = logicalRect.Contains(event.GetPosition());
7474 if (isInCompass) {
7475 if (m_Compass->MouseEvent(event)) {
7476 cursor_region = CENTER;
7477 if (!g_btouch) SetCanvasCursor(event);
7478 return true;
7479 }
7480 }
7481 }
7482
7483 if (m_notification_button && m_notification_button->IsShown()) {
7484 wxRect logicalRect = m_notification_button->GetLogicalRect();
7485 bool isinButton = logicalRect.Contains(event.GetPosition());
7486 if (isinButton) {
7487 SetCursor(*pCursorArrow);
7488 if (event.LeftDown()) HandleNotificationMouseClick();
7489 return true;
7490 }
7491 }
7492
7493 if (MouseEventToolbar(event)) return true;
7494
7495 if (MouseEventChartBar(event)) return true;
7496
7497 if (MouseEventMUIBar(event)) return true;
7498
7499 if (MouseEventIENCBar(event)) return true;
7500 }
7501 return false;
7502}
7503
7504void ChartCanvas::HandleNotificationMouseClick() {
7505 if (!m_NotificationsList) {
7506 m_NotificationsList = new NotificationsList(this);
7507
7508 // calculate best size for Notification list
7509 m_NotificationsList->RecalculateSize();
7510 m_NotificationsList->Hide();
7511 }
7512
7513 if (m_NotificationsList->IsShown()) {
7514 m_NotificationsList->Hide();
7515 } else {
7516 m_NotificationsList->RecalculateSize();
7517 m_NotificationsList->ReloadNotificationList();
7518 m_NotificationsList->Show();
7519 }
7520}
7521bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7522 if (!g_bShowChartBar) return false;
7523
7524 if (!m_Piano->MouseEvent(event)) return false;
7525
7526 cursor_region = CENTER;
7527 if (!g_btouch) SetCanvasCursor(event);
7528 return true;
7529}
7530
7531bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7532 if (!IsPrimaryCanvas()) return false;
7533
7534 if (g_MainToolbar) {
7535 if (!g_MainToolbar->MouseEvent(event))
7536 return false;
7537 else
7538 g_MainToolbar->RefreshToolbar();
7539 }
7540
7541 cursor_region = CENTER;
7542 if (!g_btouch) SetCanvasCursor(event);
7543 return true;
7544}
7545
7546bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7547 if (!IsPrimaryCanvas()) return false;
7548
7549 if (g_iENCToolbar) {
7550 if (!g_iENCToolbar->MouseEvent(event))
7551 return false;
7552 else {
7553 g_iENCToolbar->RefreshToolbar();
7554 return true;
7555 }
7556 }
7557 return false;
7558}
7559
7560bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7561 if (m_muiBar) {
7562 if (!m_muiBar->MouseEvent(event)) return false;
7563 }
7564
7565 cursor_region = CENTER;
7566 if (!g_btouch) SetCanvasCursor(event);
7567 if (m_muiBar)
7568 return true;
7569 else
7570 return false;
7571}
7572
7573bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7574 int x, y;
7575
7576 bool bret = false;
7577
7578 event.GetPosition(&x, &y);
7579
7580 x *= m_displayScale;
7581 y *= m_displayScale;
7582
7583 m_MouseDragging = event.Dragging();
7584
7585 // Some systems produce null drag events, where the pointer position has not
7586 // changed from the previous value. Detect this case, and abort further
7587 // processing (FS#1748)
7588#ifdef __WXMSW__
7589 if (event.Dragging()) {
7590 if ((x == mouse_x) && (y == mouse_y)) return true;
7591 }
7592#endif
7593
7594 mouse_x = x;
7595 mouse_y = y;
7596 mouse_leftisdown = event.LeftDown();
7598
7599 // Establish the event region
7600 cursor_region = CENTER;
7601
7602 int chartbar_height = GetChartbarHeight();
7603
7604 if (m_Compass && m_Compass->IsShown() &&
7605 m_Compass->GetRect().Contains(event.GetPosition())) {
7606 cursor_region = CENTER;
7607 } else if (x > xr_margin) {
7608 cursor_region = MID_RIGHT;
7609 } else if (x < xl_margin) {
7610 cursor_region = MID_LEFT;
7611 } else if (y > yb_margin - chartbar_height &&
7612 y < m_canvas_height - chartbar_height) {
7613 cursor_region = MID_TOP;
7614 } else if (y < yt_margin) {
7615 cursor_region = MID_BOT;
7616 } else {
7617 cursor_region = CENTER;
7618 }
7619
7620 if (!g_btouch) SetCanvasCursor(event);
7621
7622 // Protect from leftUp's coming from event handlers in child
7623 // windows who return focus to the canvas.
7624 leftIsDown = event.LeftDown();
7625
7626#ifndef __WXOSX__
7627 if (event.LeftDown()) {
7628 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7629 // The menu bar is temporarily visible due to alt having been pressed.
7630 // Clicking will hide it, and do nothing else.
7631 g_bTempShowMenuBar = false;
7632 parent_frame->ApplyGlobalSettings(false);
7633 return (true);
7634 }
7635 }
7636#endif
7637
7638 // Update modifiers here; some window managers never send the key event
7639 m_modkeys = 0;
7640 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7641 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7642
7643#ifdef __WXMSW__
7644 // TODO Test carefully in other platforms, remove ifdef....
7645 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7646 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7647#endif
7648
7649 event.SetEventObject(this);
7650 if (SendMouseEventToPlugins(event))
7651 return (true); // PlugIn did something, and does not want the canvas to
7652 // do anything else
7653
7654 // Capture LeftUp's and time them, unless it already came from the timer.
7655
7656 // Detect end of chart dragging
7657 if (g_btouch && m_bChartDragging && event.LeftUp()) {
7658 StartChartDragInertia();
7659 }
7660
7661 if (b_handle_dclick && event.LeftUp() && !singleClickEventIsValid) {
7662 // Ignore the second LeftUp after the DClick.
7663 if (m_DoubleClickTimer->IsRunning()) {
7664 m_DoubleClickTimer->Stop();
7665 return (true);
7666 }
7667
7668 // Save the event for later running if there is no DClick.
7669 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7670 singleClickEvent = event;
7671 singleClickEventIsValid = true;
7672 return (true);
7673 }
7674
7675 // This logic is necessary on MSW to handle the case where
7676 // a context (right-click) menu is dismissed without action
7677 // by clicking on the chart surface.
7678 // We need to avoid an unintentional pan by eating some clicks...
7679#ifdef __WXMSW__
7680 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7681 if (g_click_stop > 0) {
7682 g_click_stop--;
7683 return (true);
7684 }
7685 }
7686#endif
7687
7688 // Kick off the Rotation control timer
7689 if (GetUpMode() == COURSE_UP_MODE) {
7690 m_b_rot_hidef = false;
7691 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7692 } else
7693 pRotDefTimer->Stop();
7694
7695 // Retrigger the route leg / AIS target popup timer
7696 bool bRoll = !g_btouch;
7697#ifdef __ANDROID__
7698 bRoll = g_bRollover;
7699#endif
7700 if (bRoll) {
7701 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7702 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7703 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7704 m_RolloverPopupTimer.Start(
7705 10,
7706 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7707 else
7708 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7709 }
7710
7711 // Retrigger the cursor tracking timer
7712 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7713
7714// Show cursor position on Status Bar, if present
7715// except for GTK, under which status bar updates are very slow
7716// due to Update() call.
7717// In this case, as a workaround, update the status window
7718// after an interval timer (pCurTrackTimer) pops, which will happen
7719// whenever the mouse has stopped moving for specified interval.
7720// See the method OnCursorTrackTimerEvent()
7721#if !defined(__WXGTK__) && !defined(__WXQT__)
7722 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7723#endif
7724
7725 // Send the current cursor lat/lon to all PlugIns requesting it
7726 if (g_pi_manager) {
7727 // Occasionally, MSW will produce nonsense events on right click....
7728 // This results in an error in cursor geo position, so we skip this case
7729 if ((x >= 0) && (y >= 0))
7730 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7731 }
7732
7733 if (!g_btouch) {
7734 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7735 wxPoint p = ClientToScreen(wxPoint(x, y));
7736 }
7737 }
7738
7739 if (1 ) {
7740 // Route Creation Rubber Banding
7741 if (m_routeState >= 2) {
7742 r_rband.x = x;
7743 r_rband.y = y;
7744 m_bDrawingRoute = true;
7745
7746 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7747 Refresh(false);
7748 }
7749
7750 // Measure Tool Rubber Banding
7751 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7752 r_rband.x = x;
7753 r_rband.y = y;
7754 m_bDrawingRoute = true;
7755
7756 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7757 Refresh(false);
7758 }
7759 }
7760 return bret;
7761}
7762
7763void ChartCanvas::CallPopupMenu(int x, int y) {
7764 int mx, my;
7765 mx = x;
7766 my = y;
7767
7768 last_drag.x = mx;
7769 last_drag.y = my;
7770 if (m_routeState) { // creating route?
7771 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
7772 return;
7773 }
7774 // General Right Click
7775 // Look for selectable objects
7776 double slat, slon;
7777 slat = m_cursor_lat;
7778 slon = m_cursor_lon;
7779
7780#if defined(__WXMAC__) || defined(__ANDROID__)
7781 wxScreenDC sdc;
7782 ocpnDC dc(sdc);
7783#else
7784 wxClientDC cdc(GetParent());
7785 ocpnDC dc(cdc);
7786#endif
7787
7788 SelectItem *pFindAIS;
7789 SelectItem *pFindRP;
7790 SelectItem *pFindRouteSeg;
7791 SelectItem *pFindTrackSeg;
7792 SelectItem *pFindCurrent = NULL;
7793 SelectItem *pFindTide = NULL;
7794
7795 // Deselect any current objects
7796 if (m_pSelectedRoute) {
7797 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7798 m_pSelectedRoute->DeSelectRoute();
7799#ifdef ocpnUSE_GL
7800 if (g_bopengl && m_glcc) {
7801 InvalidateGL();
7802 Update();
7803 } else
7804#endif
7805 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7806 }
7807
7808 if (m_pFoundRoutePoint) {
7809 m_pFoundRoutePoint->m_bPtIsSelected = false;
7810 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
7811 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
7812 }
7813
7816 if (g_btouch && m_pRoutePointEditTarget) {
7817 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7818 m_pRoutePointEditTarget->m_bPtIsSelected = false;
7819 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7820 }
7821
7822 // Get all the selectable things at the cursor
7823 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7824 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7825 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7826 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7827 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7828
7829 if (m_bShowCurrent)
7830 pFindCurrent =
7831 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7832
7833 if (m_bShowTide) // look for tide stations
7834 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7835
7836 int seltype = 0;
7837
7838 // Try for AIS targets first
7839 if (pFindAIS) {
7840 m_FoundAIS_MMSI = pFindAIS->GetUserData();
7841
7842 // Make sure the target data is available
7843 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
7844 seltype |= SELTYPE_AISTARGET;
7845 }
7846
7847 // Now the various Route Parts
7848
7849 m_pFoundRoutePoint = NULL;
7850 if (pFindRP) {
7851 RoutePoint *pFirstVizPoint = NULL;
7852 RoutePoint *pFoundActiveRoutePoint = NULL;
7853 RoutePoint *pFoundVizRoutePoint = NULL;
7854 Route *pSelectedActiveRoute = NULL;
7855 Route *pSelectedVizRoute = NULL;
7856
7857 // There is at least one routepoint, so get the whole list
7858 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7859 SelectableItemList SelList =
7860 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7861 wxSelectableItemListNode *node = SelList.GetFirst();
7862 while (node) {
7863 SelectItem *pFindSel = node->GetData();
7864
7865 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7866
7867 // Get an array of all routes using this point
7868 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7869
7870 // Use route array (if any) to determine actual visibility for this point
7871 bool brp_viz = false;
7872 if (proute_array) {
7873 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7874 Route *pr = (Route *)proute_array->Item(ir);
7875 if (pr->IsVisible()) {
7876 brp_viz = true;
7877 break;
7878 }
7879 }
7880 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7881 // but still exists as a waypoint
7882 brp_viz = prp->IsVisible(); // so treat as isolated point
7883
7884 } else
7885 brp_viz = prp->IsVisible(); // isolated point
7886
7887 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7888
7889 // Use route array to choose the appropriate route
7890 // Give preference to any active route, otherwise select the first visible
7891 // route in the array for this point
7892 m_pSelectedRoute = NULL;
7893 if (proute_array) {
7894 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7895 Route *pr = (Route *)proute_array->Item(ir);
7896 if (pr->m_bRtIsActive) {
7897 pSelectedActiveRoute = pr;
7898 pFoundActiveRoutePoint = prp;
7899 break;
7900 }
7901 }
7902
7903 if (NULL == pSelectedVizRoute) {
7904 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7905 Route *pr = (Route *)proute_array->Item(ir);
7906 if (pr->IsVisible()) {
7907 pSelectedVizRoute = pr;
7908 pFoundVizRoutePoint = prp;
7909 break;
7910 }
7911 }
7912 }
7913
7914 delete proute_array;
7915 }
7916
7917 node = node->GetNext();
7918 }
7919
7920 // Now choose the "best" selections
7921 if (pFoundActiveRoutePoint) {
7922 m_pFoundRoutePoint = pFoundActiveRoutePoint;
7923 m_pSelectedRoute = pSelectedActiveRoute;
7924 } else if (pFoundVizRoutePoint) {
7925 m_pFoundRoutePoint = pFoundVizRoutePoint;
7926 m_pSelectedRoute = pSelectedVizRoute;
7927 } else
7928 // default is first visible point in list
7929 m_pFoundRoutePoint = pFirstVizPoint;
7930
7931 if (m_pSelectedRoute) {
7932 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7933 } else if (m_pFoundRoutePoint)
7934 seltype |= SELTYPE_MARKPOINT;
7935
7936 // Highlite the selected point, to verify the proper right click
7937 // selection
7938 if (m_pFoundRoutePoint) {
7939 m_pFoundRoutePoint->m_bPtIsSelected = true;
7940 wxRect wp_rect;
7941 RoutePointGui(*m_pFoundRoutePoint)
7942 .CalculateDCRect(m_dc_route, this, &wp_rect);
7943 RefreshRect(wp_rect, true);
7944 }
7945 }
7946
7947 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7948 // routes But call the popup handler with identifier appropriate to the type
7949 if (pFindRouteSeg) // there is at least one select item
7950 {
7951 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7952 SelectableItemList SelList =
7953 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7954
7955 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
7956 {
7957 // Choose the first visible route containing segment in the list
7958 wxSelectableItemListNode *node = SelList.GetFirst();
7959 while (node) {
7960 SelectItem *pFindSel = node->GetData();
7961
7962 Route *pr = (Route *)pFindSel->m_pData3;
7963 if (pr->IsVisible()) {
7964 m_pSelectedRoute = pr;
7965 break;
7966 }
7967 node = node->GetNext();
7968 }
7969 }
7970
7971 if (m_pSelectedRoute) {
7972 if (NULL == m_pFoundRoutePoint)
7973 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7974
7975 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7976 if (m_pSelectedRoute->m_bRtIsSelected) {
7977#ifdef ocpnUSE_GL
7978 if (g_bopengl && m_glcc) {
7979 InvalidateGL();
7980 Update();
7981 } else
7982#endif
7983 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7984 }
7985
7986 seltype |= SELTYPE_ROUTESEGMENT;
7987 }
7988 }
7989
7990 if (pFindTrackSeg) {
7991 m_pSelectedTrack = NULL;
7992 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7993 SelectableItemList SelList =
7994 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7995
7996 // Choose the first visible track containing segment in the list
7997 wxSelectableItemListNode *node = SelList.GetFirst();
7998 while (node) {
7999 SelectItem *pFindSel = node->GetData();
8000
8001 Track *pt = (Track *)pFindSel->m_pData3;
8002 if (pt->IsVisible()) {
8003 m_pSelectedTrack = pt;
8004 break;
8005 }
8006 node = node->GetNext();
8007 }
8008
8009 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8010 }
8011
8012 bool bseltc = false;
8013 // if(0 == seltype)
8014 {
8015 if (pFindCurrent) {
8016 // There may be multiple current entries at the same point.
8017 // For example, there often is a current substation (with directions
8018 // specified) co-located with its master. We want to select the
8019 // substation, so that the direction will be properly indicated on the
8020 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8021 // substation)
8022 IDX_entry *pIDX_best_candidate;
8023
8024 SelectItem *pFind = NULL;
8025 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8026 SelectableItemList SelList = pSelectTC->FindSelectionList(
8027 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_CURRENTPOINT);
8028
8029 // Default is first entry
8030 wxSelectableItemListNode *node = SelList.GetFirst();
8031 pFind = node->GetData();
8032 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8033
8034 if (SelList.GetCount() > 1) {
8035 node = node->GetNext();
8036 while (node) {
8037 pFind = node->GetData();
8038 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8039 if (pIDX_candidate->IDX_type == 'c') {
8040 pIDX_best_candidate = pIDX_candidate;
8041 break;
8042 }
8043
8044 node = node->GetNext();
8045 } // while (node)
8046 } else {
8047 wxSelectableItemListNode *node = SelList.GetFirst();
8048 pFind = node->GetData();
8049 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8050 }
8051
8052 m_pIDXCandidate = pIDX_best_candidate;
8053
8054 if (0 == seltype) {
8055 DrawTCWindow(x, y, (void *)pIDX_best_candidate);
8056 Refresh(false);
8057 bseltc = true;
8058 } else
8059 seltype |= SELTYPE_CURRENTPOINT;
8060 }
8061
8062 else if (pFindTide) {
8063 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8064
8065 if (0 == seltype) {
8066 DrawTCWindow(x, y, (void *)pFindTide->m_pData1);
8067 Refresh(false);
8068 bseltc = true;
8069 } else
8070 seltype |= SELTYPE_TIDEPOINT;
8071 }
8072 }
8073
8074 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8075
8076 if (!bseltc) {
8077 InvokeCanvasMenu(x, y, seltype);
8078
8079 // Clean up if not deleted in InvokeCanvasMenu
8080 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8081 m_pSelectedRoute->m_bRtIsSelected = false;
8082 }
8083
8084 m_pSelectedRoute = NULL;
8085
8086 if (m_pFoundRoutePoint) {
8087 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8088 m_pFoundRoutePoint->m_bPtIsSelected = false;
8089 }
8090 m_pFoundRoutePoint = NULL;
8091
8092 Refresh(true);
8093 }
8094
8095 // Seth: Is this refresh needed?
8096 Refresh(false); // needed for MSW, not GTK Why??
8097}
8098bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8099 // For now just bail out completely if the point clicked is not on the chart
8100 if (std::isnan(m_cursor_lat)) return false;
8101
8102 // Mouse Clicks
8103 bool ret = false; // return true if processed
8104
8105 int x, y, mx, my;
8106 event.GetPosition(&x, &y);
8107 mx = x;
8108 my = y;
8109
8110 // Calculate meaningful SelectRadius
8111 float SelectRadius;
8112 SelectRadius = g_Platform->GetSelectRadiusPix() /
8113 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8114
8116 // We start with Double Click processing. The first left click just starts a
8117 // timer and is remembered, then we actually do something if there is a
8118 // LeftDClick. If there is, the two single clicks are ignored.
8119
8120 if (event.LeftDClick() && (cursor_region == CENTER)) {
8121 m_DoubleClickTimer->Start();
8122 singleClickEventIsValid = false;
8123
8124 double zlat, zlon;
8125 GetCanvasPixPoint(x * g_current_monitor_dip_px_ratio,
8126 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8127
8128 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8129 if (m_bShowAIS) {
8130 SelectItem *pFindAIS;
8131 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8132
8133 if (pFindAIS) {
8134 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8135 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8136 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8137 }
8138 return true;
8139 }
8140 }
8141
8142 SelectableItemList rpSelList =
8143 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8144 wxSelectableItemListNode *node = rpSelList.GetFirst();
8145 bool b_onRPtarget = false;
8146 while (node) {
8147 SelectItem *pFind = node->GetData();
8148 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8149 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8150 b_onRPtarget = true;
8151 break;
8152 }
8153 node = node->GetNext();
8154 }
8155
8156 // Double tap with selected RoutePoint or Mark
8157
8158 if (m_pRoutePointEditTarget) {
8159 if (b_onRPtarget) {
8160 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8161 return true;
8162 } else {
8163 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8164 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8165 if (g_btouch)
8166 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8167 wxRect wp_rect;
8168 RoutePointGui(*m_pRoutePointEditTarget)
8169 .CalculateDCRect(m_dc_route, this, &wp_rect);
8170 m_pRoutePointEditTarget = NULL; // cancel selection
8171 RefreshRect(wp_rect, true);
8172 return true;
8173 }
8174 } else {
8175 node = rpSelList.GetFirst();
8176 if (node) {
8177 SelectItem *pFind = node->GetData();
8178 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8179 if (frp) {
8180 wxArrayPtrVoid *proute_array =
8181 g_pRouteMan->GetRouteArrayContaining(frp);
8182
8183 // Use route array (if any) to determine actual visibility for this
8184 // point
8185 bool brp_viz = false;
8186 if (proute_array) {
8187 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8188 Route *pr = (Route *)proute_array->Item(ir);
8189 if (pr->IsVisible()) {
8190 brp_viz = true;
8191 break;
8192 }
8193 }
8194 delete proute_array;
8195 if (!brp_viz &&
8196 frp->IsShared()) // is not visible as part of route, but still
8197 // exists as a waypoint
8198 brp_viz = frp->IsVisible(); // so treat as isolated point
8199 } else
8200 brp_viz = frp->IsVisible(); // isolated point
8201
8202 if (brp_viz) {
8203 ShowMarkPropertiesDialog(frp);
8204 return true;
8205 }
8206 }
8207 }
8208 }
8209
8210 SelectItem *cursorItem;
8211 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8212
8213 if (cursorItem) {
8214 Route *pr = (Route *)cursorItem->m_pData3;
8215 if (pr->IsVisible()) {
8216 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8217 return true;
8218 }
8219 }
8220
8221 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8222
8223 if (cursorItem) {
8224 Track *pt = (Track *)cursorItem->m_pData3;
8225 if (pt->IsVisible()) {
8226 ShowTrackPropertiesDialog(pt);
8227 return true;
8228 }
8229 }
8230
8231 // Found no object to act on, so show chart info.
8232
8233 ShowObjectQueryWindow(x, y, zlat, zlon);
8234 return true;
8235 }
8236
8238 if (event.LeftDown()) {
8239 // This really should not be needed, but....
8240 // on Windows, when using wxAUIManager, sometimes the focus is lost
8241 // when clicking into another pane, e.g.the AIS target list, and then back
8242 // to this pane. Oddly, some mouse events are not lost, however. Like this
8243 // one....
8244 SetFocus();
8245
8246 last_drag.x = mx;
8247 last_drag.y = my;
8248 leftIsDown = true;
8249
8250 if (!g_btouch) {
8251 if (m_routeState) // creating route?
8252 {
8253 double rlat, rlon;
8254 bool appending = false;
8255 bool inserting = false;
8256 Route *tail = 0;
8257
8258 SetCursor(*pCursorPencil);
8259 rlat = m_cursor_lat;
8260 rlon = m_cursor_lon;
8261
8262 m_bRouteEditing = true;
8263
8264 if (m_routeState == 1) {
8265 m_pMouseRoute = new Route();
8266 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8267 pRouteList->Append(m_pMouseRoute);
8268 r_rband.x = x;
8269 r_rband.y = y;
8270 }
8271
8272 // Check to see if there is a nearby point which may be reused
8273 RoutePoint *pMousePoint = NULL;
8274
8275 // Calculate meaningful SelectRadius
8276 double nearby_radius_meters =
8277 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8278
8279 RoutePoint *pNearbyPoint =
8280 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8281 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8282 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8283 wxArrayPtrVoid *proute_array =
8284 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8285
8286 // Use route array (if any) to determine actual visibility for this
8287 // point
8288 bool brp_viz = false;
8289 if (proute_array) {
8290 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8291 Route *pr = (Route *)proute_array->Item(ir);
8292 if (pr->IsVisible()) {
8293 brp_viz = true;
8294 break;
8295 }
8296 }
8297 delete proute_array;
8298 if (!brp_viz &&
8299 pNearbyPoint->IsShared()) // is not visible as part of route,
8300 // but still exists as a waypoint
8301 brp_viz =
8302 pNearbyPoint->IsVisible(); // so treat as isolated point
8303 } else
8304 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8305
8306 if (brp_viz) {
8307 wxString msg = _("Use nearby waypoint?");
8308 // Don't add a mark without name to the route. Name it if needed
8309 const bool noname(pNearbyPoint->GetName() == "");
8310 if (noname) {
8311 msg =
8312 _("Use nearby nameless waypoint and name it M with"
8313 " a unique number?");
8314 }
8315 // Avoid route finish on focus change for message dialog
8316 m_FinishRouteOnKillFocus = false;
8317 int dlg_return =
8318 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8319 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8320 m_FinishRouteOnKillFocus = true;
8321 if (dlg_return == wxID_YES) {
8322 if (noname) {
8323 if (m_pMouseRoute) {
8324 int last_wp_num = m_pMouseRoute->GetnPoints();
8325 // AP-ECRMB will truncate to 6 characters
8326 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8327 wxString wp_name = wxString::Format(
8328 "M%002i-%s", last_wp_num + 1, guid_short);
8329 pNearbyPoint->SetName(wp_name);
8330 } else
8331 pNearbyPoint->SetName("WPXX");
8332 }
8333 pMousePoint = pNearbyPoint;
8334
8335 // Using existing waypoint, so nothing to delete for undo.
8336 if (m_routeState > 1)
8337 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8338 Undo_HasParent, NULL);
8339
8340 tail =
8341 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8342 bool procede = false;
8343 if (tail) {
8344 procede = true;
8345 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8346 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8347 procede = false;
8348 }
8349
8350 if (procede) {
8351 int dlg_return;
8352 m_FinishRouteOnKillFocus = false;
8353 if (m_routeState ==
8354 1) { // first point in new route, preceeding route to be
8355 // added? Not touch case
8356
8357 wxString dmsg =
8358 _("Insert first part of this route in the new route?");
8359 if (tail->GetIndexOf(pMousePoint) ==
8360 tail->GetnPoints()) // Starting on last point of another
8361 // route?
8362 dmsg = _("Insert this route in the new route?");
8363
8364 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8365 dlg_return = OCPNMessageBox(
8366 this, dmsg, _("OpenCPN Route Create"),
8367 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8368 m_FinishRouteOnKillFocus = true;
8369
8370 if (dlg_return == wxID_YES) {
8371 inserting = true; // part of the other route will be
8372 // preceeding the new route
8373 }
8374 }
8375 } else {
8376 wxString dmsg =
8377 _("Append last part of this route to the new route?");
8378 if (tail->GetIndexOf(pMousePoint) == 1)
8379 dmsg = _(
8380 "Append this route to the new route?"); // Picking the
8381 // first point
8382 // of another
8383 // route?
8384
8385 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8386 dlg_return = OCPNMessageBox(
8387 this, dmsg, _("OpenCPN Route Create"),
8388 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8389 m_FinishRouteOnKillFocus = true;
8390
8391 if (dlg_return == wxID_YES) {
8392 appending = true; // part of the other route will be
8393 // appended to the new route
8394 }
8395 }
8396 }
8397 }
8398
8399 // check all other routes to see if this point appears in any
8400 // other route If it appears in NO other route, then it should e
8401 // considered an isolated mark
8402 if (!FindRouteContainingWaypoint(pMousePoint))
8403 pMousePoint->SetShared(true);
8404 }
8405 }
8406 }
8407
8408 if (NULL == pMousePoint) { // need a new point
8409 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8410 _T(""), wxEmptyString);
8411 pMousePoint->SetNameShown(false);
8412
8413 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8414
8415 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8416
8417 if (m_routeState > 1)
8418 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8419 Undo_IsOrphanded, NULL);
8420 }
8421
8422 if (m_pMouseRoute) {
8423 if (m_routeState == 1) {
8424 // First point in the route.
8425 m_pMouseRoute->AddPoint(pMousePoint);
8426 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8427 } else {
8428 if (m_pMouseRoute->m_NextLegGreatCircle) {
8429 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8430 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8431 &rhumbBearing, &rhumbDist);
8432 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8433 rlat, &gcDist, &gcBearing, NULL);
8434 double gcDistNM = gcDist / 1852.0;
8435
8436 // Empirically found expression to get reasonable route segments.
8437 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8438 pow(rhumbDist - gcDistNM - 1, 0.5);
8439
8440 wxString msg;
8441 msg << _("For this leg the Great Circle route is ")
8442 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8443 << _(" shorter than rhumbline.\n\n")
8444 << _("Would you like include the Great Circle routing points "
8445 "for this leg?");
8446
8447 m_FinishRouteOnKillFocus = false;
8448 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8449 // does not fully capture mouse
8450
8451 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8452 wxYES_NO | wxNO_DEFAULT);
8453
8454 m_disable_edge_pan = false;
8455 m_FinishRouteOnKillFocus = true;
8456
8457 if (answer == wxID_YES) {
8458 RoutePoint *gcPoint;
8459 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8460 wxRealPoint gcCoord;
8461
8462 for (int i = 1; i <= segmentCount; i++) {
8463 double fraction = (double)i * (1.0 / (double)segmentCount);
8464 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8465 gcDist * fraction, gcBearing,
8466 &gcCoord.x, &gcCoord.y, NULL);
8467
8468 if (i < segmentCount) {
8469 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
8470 _T(""), wxEmptyString);
8471 gcPoint->SetNameShown(false);
8472 // pConfig->AddNewWayPoint(gcPoint, -1);
8473 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8474
8475 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8476 gcPoint);
8477 } else {
8478 gcPoint = pMousePoint; // Last point, previously exsisting!
8479 }
8480
8481 m_pMouseRoute->AddPoint(gcPoint);
8482 pSelect->AddSelectableRouteSegment(
8483 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8484 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8485 prevGcPoint = gcPoint;
8486 }
8487
8488 undo->CancelUndoableAction(true);
8489
8490 } else {
8491 m_pMouseRoute->AddPoint(pMousePoint);
8492 pSelect->AddSelectableRouteSegment(
8493 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8494 pMousePoint, m_pMouseRoute);
8495 undo->AfterUndoableAction(m_pMouseRoute);
8496 }
8497 } else {
8498 // Ordinary rhumblinesegment.
8499 m_pMouseRoute->AddPoint(pMousePoint);
8500 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8501 rlon, m_prev_pMousePoint,
8502 pMousePoint, m_pMouseRoute);
8503 undo->AfterUndoableAction(m_pMouseRoute);
8504 }
8505 }
8506 }
8507 m_prev_rlat = rlat;
8508 m_prev_rlon = rlon;
8509 m_prev_pMousePoint = pMousePoint;
8510 if (m_pMouseRoute)
8511 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8512
8513 m_routeState++;
8514
8515 if (appending ||
8516 inserting) { // Appending a route or making a new route
8517 int connect = tail->GetIndexOf(pMousePoint);
8518 if (connect == 1) {
8519 inserting = false; // there is nothing to insert
8520 appending = true; // so append
8521 }
8522 int length = tail->GetnPoints();
8523
8524 int i;
8525 int start, stop;
8526 if (appending) {
8527 start = connect + 1;
8528 stop = length;
8529 } else { // inserting
8530 start = 1;
8531 stop = connect;
8532 m_pMouseRoute->RemovePoint(
8533 m_pMouseRoute
8534 ->GetLastPoint()); // Remove the first and only point
8535 }
8536 for (i = start; i <= stop; i++) {
8537 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8538 if (m_pMouseRoute)
8539 m_pMouseRoute->m_lastMousePointIndex =
8540 m_pMouseRoute->GetnPoints();
8541 m_routeState++;
8542 gFrame->RefreshAllCanvas();
8543 ret = true;
8544 }
8545 m_prev_rlat =
8546 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8547 m_prev_rlon =
8548 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8549 m_pMouseRoute->FinalizeForRendering();
8550 }
8551 gFrame->RefreshAllCanvas();
8552 ret = true;
8553 }
8554
8555 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8556 {
8557 SetCursor(*pCursorPencil);
8558
8559 if (!m_pMeasureRoute) {
8560 m_pMeasureRoute = new Route();
8561 pRouteList->Append(m_pMeasureRoute);
8562 }
8563
8564 if (m_nMeasureState == 1) {
8565 r_rband.x = x;
8566 r_rband.y = y;
8567 }
8568
8569 RoutePoint *pMousePoint = new RoutePoint(m_cursor_lat, m_cursor_lon,
8570 wxString(_T ( "circle" )),
8571 wxEmptyString, wxEmptyString);
8572 pMousePoint->m_bShowName = false;
8573 pMousePoint->SetShowWaypointRangeRings(false);
8574
8575 m_pMeasureRoute->AddPoint(pMousePoint);
8576
8577 m_prev_rlat = m_cursor_lat;
8578 m_prev_rlon = m_cursor_lon;
8579 m_prev_pMousePoint = pMousePoint;
8580 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8581
8582 m_nMeasureState++;
8583 gFrame->RefreshAllCanvas();
8584 ret = true;
8585 }
8586
8587 else {
8588 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8589 }
8590 } // !g_btouch
8591 else { // g_btouch
8592
8593 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8594 // if near screen edge, pan with injection
8595 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8596 // return;
8597 // }
8598 }
8599 }
8600
8601 if (ret) return true;
8602 }
8603
8604 if (event.Dragging()) {
8605 // in touch screen mode ensure the finger/cursor is on the selected point's
8606 // radius to allow dragging
8607 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8608 if (g_btouch) {
8609 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8610 SelectItem *pFind = NULL;
8611 SelectableItemList SelList = pSelect->FindSelectionList(
8612 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8613 wxSelectableItemListNode *node = SelList.GetFirst();
8614 while (node) {
8615 pFind = node->GetData();
8616 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8617 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8618 node = node->GetNext();
8619 }
8620 }
8621
8622 // Check for use of dragHandle
8623 if (m_pRoutePointEditTarget &&
8624 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8625 SelectItem *pFind = NULL;
8626 SelectableItemList SelList = pSelect->FindSelectionList(
8627 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8628 wxSelectableItemListNode *node = SelList.GetFirst();
8629 while (node) {
8630 pFind = node->GetData();
8631 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8632 if (m_pRoutePointEditTarget == frp) {
8633 m_bIsInRadius = true;
8634 break;
8635 }
8636 node = node->GetNext();
8637 }
8638
8639 if (!m_dragoffsetSet) {
8640 RoutePointGui(*m_pRoutePointEditTarget)
8641 .PresetDragOffset(this, mouse_x, mouse_y);
8642 m_dragoffsetSet = true;
8643 }
8644 }
8645 }
8646
8647 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8648 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8649
8650 if (NULL == g_pMarkInfoDialog) {
8651 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8652 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8653 DraggingAllowed = false;
8654
8655 if (m_pRoutePointEditTarget &&
8656 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8657 DraggingAllowed = false;
8658
8659 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8660
8661 if (DraggingAllowed) {
8662 if (!undo->InUndoableAction()) {
8663 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8664 Undo_NeedsCopy, m_pFoundPoint);
8665 }
8666
8667 // Get the update rectangle for the union of the un-edited routes
8668 wxRect pre_rect;
8669
8670 if (!g_bopengl && m_pEditRouteArray) {
8671 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8672 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8673 // Need to validate route pointer
8674 // Route may be gone due to drgging close to ownship with
8675 // "Delete On Arrival" state set, as in the case of
8676 // navigating to an isolated waypoint on a temporary route
8677 if (g_pRouteMan->IsRouteValid(pr)) {
8678 wxRect route_rect;
8679 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8680 pre_rect.Union(route_rect);
8681 }
8682 }
8683 }
8684
8685 double new_cursor_lat = m_cursor_lat;
8686 double new_cursor_lon = m_cursor_lon;
8687
8688 if (CheckEdgePan(x, y, true, 5, 2))
8689 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8690
8691 // update the point itself
8692 if (g_btouch) {
8693 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8694 // new_cursor_lat, new_cursor_lon);
8695 RoutePointGui(*m_pRoutePointEditTarget)
8696 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8697 // update the Drag Handle entry in the pSelect list
8698 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8699 m_pRoutePointEditTarget,
8700 SELTYPE_DRAGHANDLE);
8701 m_pFoundPoint->m_slat =
8702 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8703 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8704 } else {
8705 m_pRoutePointEditTarget->m_lat =
8706 new_cursor_lat; // update the RoutePoint entry
8707 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8708 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8709 m_pFoundPoint->m_slat =
8710 new_cursor_lat; // update the SelectList entry
8711 m_pFoundPoint->m_slon = new_cursor_lon;
8712 }
8713
8714 // Update the MarkProperties Dialog, if currently shown
8715 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8716 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8717 g_pMarkInfoDialog->UpdateProperties(true);
8718 }
8719
8720 if (g_bopengl) {
8721 // InvalidateGL();
8722 Refresh(false);
8723 } else {
8724 // Get the update rectangle for the edited route
8725 wxRect post_rect;
8726
8727 if (m_pEditRouteArray) {
8728 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8729 ir++) {
8730 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8731 if (g_pRouteMan->IsRouteValid(pr)) {
8732 wxRect route_rect;
8733 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8734 post_rect.Union(route_rect);
8735 }
8736 }
8737 }
8738
8739 // Invalidate the union region
8740 pre_rect.Union(post_rect);
8741 RefreshRect(pre_rect, false);
8742 }
8743 gFrame->RefreshCanvasOther(this);
8744 m_bRoutePoinDragging = true;
8745 }
8746 ret = true;
8747 } // if Route Editing
8748
8749 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8750 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8751
8752 if (NULL == g_pMarkInfoDialog) {
8753 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8754 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8755 DraggingAllowed = false;
8756
8757 if (m_pRoutePointEditTarget &&
8758 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8759 DraggingAllowed = false;
8760
8761 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8762
8763 if (DraggingAllowed) {
8764 if (!undo->InUndoableAction()) {
8765 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8766 Undo_NeedsCopy, m_pFoundPoint);
8767 }
8768
8769 // The mark may be an anchorwatch
8770 double lpp1 = 0.;
8771 double lpp2 = 0.;
8772 double lppmax;
8773
8774 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8775 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8776 }
8777 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8778 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8779 }
8780 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8781
8782 // Get the update rectangle for the un-edited mark
8783 wxRect pre_rect;
8784 if (!g_bopengl) {
8785 RoutePointGui(*m_pRoutePointEditTarget)
8786 .CalculateDCRect(m_dc_route, this, &pre_rect);
8787 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8788 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8789 (int)(lppmax - (pre_rect.height / 2)));
8790 }
8791
8792 // update the point itself
8793 if (g_btouch) {
8794 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8795 // m_cursor_lat, m_cursor_lon);
8796 RoutePointGui(*m_pRoutePointEditTarget)
8797 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8798 // update the Drag Handle entry in the pSelect list
8799 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
8800 m_pRoutePointEditTarget,
8801 SELTYPE_DRAGHANDLE);
8802 m_pFoundPoint->m_slat =
8803 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8804 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8805 } else {
8806 m_pRoutePointEditTarget->m_lat =
8807 m_cursor_lat; // update the RoutePoint entry
8808 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
8809 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8810 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
8811 m_pFoundPoint->m_slon = m_cursor_lon;
8812 }
8813
8814 // Update the MarkProperties Dialog, if currently shown
8815 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8816 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8817 g_pMarkInfoDialog->UpdateProperties(true);
8818 }
8819
8820 // Invalidate the union region
8821 if (g_bopengl) {
8822 if (!g_btouch) InvalidateGL();
8823 Refresh(false);
8824 } else {
8825 // Get the update rectangle for the edited mark
8826 wxRect post_rect;
8827 RoutePointGui(*m_pRoutePointEditTarget)
8828 .CalculateDCRect(m_dc_route, this, &post_rect);
8829 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
8830 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
8831 (int)(lppmax - (post_rect.height / 2)));
8832
8833 // Invalidate the union region
8834 pre_rect.Union(post_rect);
8835 RefreshRect(pre_rect, false);
8836 }
8837 gFrame->RefreshCanvasOther(this);
8838 m_bRoutePoinDragging = true;
8839 }
8840 ret = true;
8841 }
8842
8843 if (ret) return true;
8844 } // dragging
8845
8846 if (event.LeftUp()) {
8847 bool b_startedit_route = false;
8848 m_dragoffsetSet = false;
8849
8850 if (g_btouch) {
8851 m_bChartDragging = false;
8852 m_bIsInRadius = false;
8853
8854 if (m_routeState) // creating route?
8855 {
8856 if (m_bedge_pan) {
8857 m_bedge_pan = false;
8858 return false;
8859 }
8860
8861 double rlat, rlon;
8862 bool appending = false;
8863 bool inserting = false;
8864 Route *tail = 0;
8865
8866 rlat = m_cursor_lat;
8867 rlon = m_cursor_lon;
8868
8869 if (m_pRoutePointEditTarget) {
8870 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8871 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8872 if (!g_bopengl) {
8873 wxRect wp_rect;
8874 RoutePointGui(*m_pRoutePointEditTarget)
8875 .CalculateDCRect(m_dc_route, this, &wp_rect);
8876 RefreshRect(wp_rect, true);
8877 }
8878 m_pRoutePointEditTarget = NULL;
8879 }
8880 m_bRouteEditing = true;
8881
8882 if (m_routeState == 1) {
8883 m_pMouseRoute = new Route();
8884 m_pMouseRoute->SetHiLite(50);
8885 pRouteList->Append(m_pMouseRoute);
8886 r_rband.x = x;
8887 r_rband.y = y;
8888 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8889 }
8890
8891 // Check to see if there is a nearby point which may be reused
8892 RoutePoint *pMousePoint = NULL;
8893
8894 // Calculate meaningful SelectRadius
8895 double nearby_radius_meters =
8896 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8897
8898 RoutePoint *pNearbyPoint =
8899 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8900 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8901 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8902 int dlg_return;
8903#ifndef __WXOSX__
8904 m_FinishRouteOnKillFocus =
8905 false; // Avoid route finish on focus change for message dialog
8906 dlg_return = OCPNMessageBox(
8907 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
8908 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8909 m_FinishRouteOnKillFocus = true;
8910#else
8911 dlg_return = wxID_YES;
8912#endif
8913 if (dlg_return == wxID_YES) {
8914 pMousePoint = pNearbyPoint;
8915
8916 // Using existing waypoint, so nothing to delete for undo.
8917 if (m_routeState > 1)
8918 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8919 Undo_HasParent, NULL);
8920 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8921
8922 bool procede = false;
8923 if (tail) {
8924 procede = true;
8925 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8926 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8927 procede = false;
8928 }
8929
8930 if (procede) {
8931 int dlg_return;
8932 m_FinishRouteOnKillFocus = false;
8933 if (m_routeState == 1) { // first point in new route, preceeding
8934 // route to be added? touch case
8935
8936 wxString dmsg =
8937 _("Insert first part of this route in the new route?");
8938 if (tail->GetIndexOf(pMousePoint) ==
8939 tail->GetnPoints()) // Starting on last point of another
8940 // route?
8941 dmsg = _("Insert this route in the new route?");
8942
8943 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8944 dlg_return =
8945 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8946 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8947 m_FinishRouteOnKillFocus = true;
8948
8949 if (dlg_return == wxID_YES) {
8950 inserting = true; // part of the other route will be
8951 // preceeding the new route
8952 }
8953 }
8954 } else {
8955 wxString dmsg =
8956 _("Append last part of this route to the new route?");
8957 if (tail->GetIndexOf(pMousePoint) == 1)
8958 dmsg = _(
8959 "Append this route to the new route?"); // Picking the
8960 // first point of
8961 // another route?
8962
8963 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8964 dlg_return =
8965 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8966 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8967 m_FinishRouteOnKillFocus = true;
8968
8969 if (dlg_return == wxID_YES) {
8970 appending = true; // part of the other route will be
8971 // appended to the new route
8972 }
8973 }
8974 }
8975 }
8976
8977 // check all other routes to see if this point appears in any other
8978 // route If it appears in NO other route, then it should e
8979 // considered an isolated mark
8980 if (!FindRouteContainingWaypoint(pMousePoint))
8981 pMousePoint->SetShared(true);
8982 }
8983 }
8984
8985 if (NULL == pMousePoint) { // need a new point
8986 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8987 _T(""), wxEmptyString);
8988 pMousePoint->SetNameShown(false);
8989
8990 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8991
8992 if (m_routeState > 1)
8993 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8994 Undo_IsOrphanded, NULL);
8995 }
8996
8997 if (m_routeState == 1) {
8998 // First point in the route.
8999 m_pMouseRoute->AddPoint(pMousePoint);
9000 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9001
9002 } else {
9003 if (m_pMouseRoute->m_NextLegGreatCircle) {
9004 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9005 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9006 &rhumbBearing, &rhumbDist);
9007 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9008 &gcDist, &gcBearing, NULL);
9009 double gcDistNM = gcDist / 1852.0;
9010
9011 // Empirically found expression to get reasonable route segments.
9012 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9013 pow(rhumbDist - gcDistNM - 1, 0.5);
9014
9015 wxString msg;
9016 msg << _("For this leg the Great Circle route is ")
9017 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9018 << _(" shorter than rhumbline.\n\n")
9019 << _("Would you like include the Great Circle routing points "
9020 "for this leg?");
9021
9022#ifndef __WXOSX__
9023 m_FinishRouteOnKillFocus = false;
9024 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9025 wxYES_NO | wxNO_DEFAULT);
9026 m_FinishRouteOnKillFocus = true;
9027#else
9028 int answer = wxID_NO;
9029#endif
9030
9031 if (answer == wxID_YES) {
9032 RoutePoint *gcPoint;
9033 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9034 wxRealPoint gcCoord;
9035
9036 for (int i = 1; i <= segmentCount; i++) {
9037 double fraction = (double)i * (1.0 / (double)segmentCount);
9038 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9039 gcDist * fraction, gcBearing,
9040 &gcCoord.x, &gcCoord.y, NULL);
9041
9042 if (i < segmentCount) {
9043 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
9044 _T(""), wxEmptyString);
9045 gcPoint->SetNameShown(false);
9046 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9047 gcPoint);
9048 } else {
9049 gcPoint = pMousePoint; // Last point, previously exsisting!
9050 }
9051
9052 m_pMouseRoute->AddPoint(gcPoint);
9053 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9054
9055 pSelect->AddSelectableRouteSegment(
9056 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9057 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9058 prevGcPoint = gcPoint;
9059 }
9060
9061 undo->CancelUndoableAction(true);
9062
9063 } else {
9064 m_pMouseRoute->AddPoint(pMousePoint);
9065 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9066 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9067 rlon, m_prev_pMousePoint,
9068 pMousePoint, m_pMouseRoute);
9069 undo->AfterUndoableAction(m_pMouseRoute);
9070 }
9071 } else {
9072 // Ordinary rhumblinesegment.
9073 m_pMouseRoute->AddPoint(pMousePoint);
9074 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9075
9076 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9077 rlon, m_prev_pMousePoint,
9078 pMousePoint, m_pMouseRoute);
9079 undo->AfterUndoableAction(m_pMouseRoute);
9080 }
9081 }
9082
9083 m_prev_rlat = rlat;
9084 m_prev_rlon = rlon;
9085 m_prev_pMousePoint = pMousePoint;
9086 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9087
9088 m_routeState++;
9089
9090 if (appending ||
9091 inserting) { // Appending a route or making a new route
9092 int connect = tail->GetIndexOf(pMousePoint);
9093 if (connect == 1) {
9094 inserting = false; // there is nothing to insert
9095 appending = true; // so append
9096 }
9097 int length = tail->GetnPoints();
9098
9099 int i;
9100 int start, stop;
9101 if (appending) {
9102 start = connect + 1;
9103 stop = length;
9104 } else { // inserting
9105 start = 1;
9106 stop = connect;
9107 m_pMouseRoute->RemovePoint(
9108 m_pMouseRoute
9109 ->GetLastPoint()); // Remove the first and only point
9110 }
9111 for (i = start; i <= stop; i++) {
9112 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9113 if (m_pMouseRoute)
9114 m_pMouseRoute->m_lastMousePointIndex =
9115 m_pMouseRoute->GetnPoints();
9116 m_routeState++;
9117 gFrame->RefreshAllCanvas();
9118 ret = true;
9119 }
9120 m_prev_rlat =
9121 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9122 m_prev_rlon =
9123 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9124 m_pMouseRoute->FinalizeForRendering();
9125 }
9126
9127 Refresh(true);
9128 ret = true;
9129 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9130 {
9131 if (m_bedge_pan) {
9132 m_bedge_pan = false;
9133 return false;
9134 }
9135
9136 if (m_nMeasureState == 1) {
9137 m_pMeasureRoute = new Route();
9138 pRouteList->Append(m_pMeasureRoute);
9139 r_rband.x = x;
9140 r_rband.y = y;
9141 }
9142
9143 if (m_pMeasureRoute) {
9144 RoutePoint *pMousePoint = new RoutePoint(
9145 m_cursor_lat, m_cursor_lon, wxString(_T ( "circle" )),
9146 wxEmptyString, wxEmptyString);
9147 pMousePoint->m_bShowName = false;
9148
9149 m_pMeasureRoute->AddPoint(pMousePoint);
9150
9151 m_prev_rlat = m_cursor_lat;
9152 m_prev_rlon = m_cursor_lon;
9153 m_prev_pMousePoint = pMousePoint;
9154 m_pMeasureRoute->m_lastMousePointIndex =
9155 m_pMeasureRoute->GetnPoints();
9156
9157 m_nMeasureState++;
9158 } else {
9159 CancelMeasureRoute();
9160 }
9161
9162 Refresh(true);
9163 ret = true;
9164 } else {
9165 bool bSelectAllowed = true;
9166 if (NULL == g_pMarkInfoDialog) {
9167 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9168 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9169 bSelectAllowed = false;
9170
9171 /*if this left up happens at the end of a route point dragging and if
9172 the cursor/thumb is on the draghandle icon, not on the point iself a new
9173 selection will select nothing and the drag will never be ended, so the
9174 legs around this point never selectable. At this step we don't need a
9175 new selection, just keep the previoulsly selected and dragged point */
9176 if (m_bRoutePoinDragging) bSelectAllowed = false;
9177
9178 if (bSelectAllowed) {
9179 bool b_was_editing_mark = m_bMarkEditing;
9180 bool b_was_editing_route = m_bRouteEditing;
9181 FindRoutePointsAtCursor(SelectRadius,
9182 true); // Possibly selecting a point in a
9183 // route for later dragging
9184
9185 /*route and a mark points in layer can't be dragged so should't be
9186 * selected and no draghandle icon*/
9187 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9188 m_pRoutePointEditTarget = NULL;
9189
9190 if (!b_was_editing_route) {
9191 if (m_pEditRouteArray) {
9192 b_startedit_route = true;
9193
9194 // Hide the track and route rollover during route point edit, not
9195 // needed, and may be confusing
9196 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9197 m_pTrackRolloverWin->IsActive(false);
9198 }
9199 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9200 m_pRouteRolloverWin->IsActive(false);
9201 }
9202
9203 wxRect pre_rect;
9204 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9205 ir++) {
9206 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9207 // Need to validate route pointer
9208 // Route may be gone due to drgging close to ownship with
9209 // "Delete On Arrival" state set, as in the case of
9210 // navigating to an isolated waypoint on a temporary route
9211 if (g_pRouteMan->IsRouteValid(pr)) {
9212 // pr->SetHiLite(50);
9213 wxRect route_rect;
9214 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9215 pre_rect.Union(route_rect);
9216 }
9217 }
9218 RefreshRect(pre_rect, true);
9219 }
9220 } else {
9221 b_startedit_route = false;
9222 }
9223
9224 // Mark editing
9225 if (m_pRoutePointEditTarget) {
9226 if (b_was_editing_mark ||
9227 b_was_editing_route) { // kill previous hilight
9228 if (m_lastRoutePointEditTarget) {
9229 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9230 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9231 RoutePointGui(*m_lastRoutePointEditTarget)
9232 .EnableDragHandle(false);
9233 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9234 SELTYPE_DRAGHANDLE);
9235 }
9236 }
9237
9238 if (m_pRoutePointEditTarget) {
9239 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9240 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9241 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9242 wxPoint2DDouble dragHandlePoint =
9243 RoutePointGui(*m_pRoutePointEditTarget)
9244 .GetDragHandlePoint(this);
9245 pSelect->AddSelectablePoint(
9246 dragHandlePoint.m_y, dragHandlePoint.m_x,
9247 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9248 }
9249 } else { // Deselect everything
9250 if (m_lastRoutePointEditTarget) {
9251 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9252 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9253 RoutePointGui(*m_lastRoutePointEditTarget)
9254 .EnableDragHandle(false);
9255 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9256 SELTYPE_DRAGHANDLE);
9257
9258 // Clear any routes being edited, probably orphans
9259 wxArrayPtrVoid *lastEditRouteArray =
9260 g_pRouteMan->GetRouteArrayContaining(
9261 m_lastRoutePointEditTarget);
9262 if (lastEditRouteArray) {
9263 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9264 ir++) {
9265 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9266 if (g_pRouteMan->IsRouteValid(pr)) {
9267 pr->m_bIsBeingEdited = false;
9268 }
9269 }
9270 delete lastEditRouteArray;
9271 }
9272 }
9273 }
9274
9275 // Do the refresh
9276
9277 if (g_bopengl) {
9278 InvalidateGL();
9279 Refresh(false);
9280 } else {
9281 if (m_lastRoutePointEditTarget) {
9282 wxRect wp_rect;
9283 RoutePointGui(*m_lastRoutePointEditTarget)
9284 .CalculateDCRect(m_dc_route, this, &wp_rect);
9285 RefreshRect(wp_rect, true);
9286 }
9287
9288 if (m_pRoutePointEditTarget) {
9289 wxRect wp_rect;
9290 RoutePointGui(*m_pRoutePointEditTarget)
9291 .CalculateDCRect(m_dc_route, this, &wp_rect);
9292 RefreshRect(wp_rect, true);
9293 }
9294 }
9295 }
9296 } // bSelectAllowed
9297
9298 // Check to see if there is a route or AIS target under the cursor
9299 // If so, start the rollover timer which creates the popup
9300 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9301 bool b_start_rollover = false;
9302 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9303 SelectItem *pFind = pSelectAIS->FindSelection(
9304 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9305 if (pFind) b_start_rollover = true;
9306 }
9307
9308 if (!b_start_rollover && !b_startedit_route) {
9309 SelectableItemList SelList = pSelect->FindSelectionList(
9310 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9311 wxSelectableItemListNode *node = SelList.GetFirst();
9312 while (node) {
9313 SelectItem *pFindSel = node->GetData();
9314
9315 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9316
9317 if (pr && pr->IsVisible()) {
9318 b_start_rollover = true;
9319 break;
9320 }
9321 node = node->GetNext();
9322 } // while
9323 }
9324
9325 if (!b_start_rollover && !b_startedit_route) {
9326 SelectableItemList SelList = pSelect->FindSelectionList(
9327 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9328 wxSelectableItemListNode *node = SelList.GetFirst();
9329 while (node) {
9330 SelectItem *pFindSel = node->GetData();
9331
9332 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9333
9334 if (tr && tr->IsVisible()) {
9335 b_start_rollover = true;
9336 break;
9337 }
9338 node = node->GetNext();
9339 } // while
9340 }
9341
9342 if (b_start_rollover)
9343 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9344 wxTIMER_ONE_SHOT);
9345 Route *tail = 0;
9346 Route *current = 0;
9347 bool appending = false;
9348 bool inserting = false;
9349 int connect = 0;
9350 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9351 // drag
9352 if (m_pRoutePointEditTarget) {
9353 // Check to see if there is a nearby point which may replace the
9354 // dragged one
9355 RoutePoint *pMousePoint = NULL;
9356
9357 int index_last;
9358 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9359 double nearby_radius_meters =
9360 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9361 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9362 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9363 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9364 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9365 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9366 bool duplicate =
9367 false; // ensure we won't create duplicate point in routes
9368 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9369 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9370 ir++) {
9371 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9372 if (pr && pr->pRoutePointList) {
9373 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9374 wxNOT_FOUND) {
9375 duplicate = true;
9376 break;
9377 }
9378 }
9379 }
9380 }
9381
9382 // Special case:
9383 // Allow "re-use" of a route's waypoints iff it is a simple
9384 // isolated route. This allows, for instance, creation of a closed
9385 // polygon route
9386 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9387
9388 if (!duplicate) {
9389 int dlg_return;
9390 dlg_return =
9391 OCPNMessageBox(this,
9392 _("Replace this RoutePoint by the nearby "
9393 "Waypoint?"),
9394 _("OpenCPN RoutePoint change"),
9395 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9396 if (dlg_return == wxID_YES) {
9397 /*double confirmation if the dragged point has been manually
9398 * created which can be important and could be deleted
9399 * unintentionally*/
9400
9401 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9402 pNearbyPoint);
9403 current =
9404 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9405
9406 if (tail && current && (tail != current)) {
9407 int dlg_return1;
9408 connect = tail->GetIndexOf(pNearbyPoint);
9409 int index_current_route =
9410 current->GetIndexOf(m_pRoutePointEditTarget);
9411 index_last = current->GetIndexOf(current->GetLastPoint());
9412 dlg_return1 = wxID_NO;
9413 if (index_last ==
9414 index_current_route) { // we are dragging the last
9415 // point of the route
9416 if (connect != tail->GetnPoints()) { // anything to do?
9417
9418 wxString dmsg(
9419 _("Last part of route to be appended to dragged "
9420 "route?"));
9421 if (connect == 1)
9422 dmsg =
9423 _("Full route to be appended to dragged route?");
9424
9425 dlg_return1 = OCPNMessageBox(
9426 this, dmsg, _("OpenCPN Route Create"),
9427 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9428 if (dlg_return1 == wxID_YES) {
9429 appending = true;
9430 }
9431 }
9432 } else if (index_current_route ==
9433 1) { // dragging the first point of the route
9434 if (connect != 1) { // anything to do?
9435
9436 wxString dmsg(
9437 _("First part of route to be inserted into dragged "
9438 "route?"));
9439 if (connect == tail->GetnPoints())
9440 dmsg = _(
9441 "Full route to be inserted into dragged route?");
9442
9443 dlg_return1 = OCPNMessageBox(
9444 this, dmsg, _("OpenCPN Route Create"),
9445 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9446 if (dlg_return1 == wxID_YES) {
9447 inserting = true;
9448 }
9449 }
9450 }
9451 }
9452
9453 if (m_pRoutePointEditTarget->IsShared()) {
9454 // dlg_return = wxID_NO;
9455 dlg_return = OCPNMessageBox(
9456 this,
9457 _("Do you really want to delete and replace this "
9458 "WayPoint") +
9459 _T("\n") + _("which has been created manually?"),
9460 ("OpenCPN RoutePoint warning"),
9461 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9462 }
9463 }
9464 if (dlg_return == wxID_YES) {
9465 pMousePoint = pNearbyPoint;
9466 if (pMousePoint->m_bIsolatedMark) {
9467 pMousePoint->SetShared(true);
9468 }
9469 pMousePoint->m_bIsolatedMark =
9470 false; // definitely no longer isolated
9471 pMousePoint->m_bIsInRoute = true;
9472 }
9473 }
9474 }
9475 }
9476 if (!pMousePoint)
9477 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9478
9479 if (m_pEditRouteArray) {
9480 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9481 ir++) {
9482 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9483 if (g_pRouteMan->IsRouteValid(pr)) {
9484 if (pMousePoint) { // remove the dragged point and insert the
9485 // nearby
9486 int nRP =
9487 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9488
9489 pSelect->DeleteAllSelectableRoutePoints(pr);
9490 pSelect->DeleteAllSelectableRouteSegments(pr);
9491
9492 pr->pRoutePointList->Insert(nRP, pMousePoint);
9493 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9494
9495 pSelect->AddAllSelectableRouteSegments(pr);
9496 pSelect->AddAllSelectableRoutePoints(pr);
9497 }
9498 pr->FinalizeForRendering();
9499 pr->UpdateSegmentDistances();
9500 if (m_bRoutePoinDragging) {
9501 // pConfig->UpdateRoute(pr);
9502 NavObj_dB::GetInstance().UpdateRoute(pr);
9503 }
9504 }
9505 }
9506 }
9507
9508 // Update the RouteProperties Dialog, if currently shown
9509 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9510 if (m_pEditRouteArray) {
9511 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9512 ir++) {
9513 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9514 if (g_pRouteMan->IsRouteValid(pr)) {
9515 if (pRoutePropDialog->GetRoute() == pr) {
9516 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9517 }
9518 /* cannot edit track points anyway
9519 else if ( ( NULL !=
9520 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9521 pTrackPropDialog->m_pTrack == pr ) {
9522 pTrackPropDialog->SetTrackAndUpdate(
9523 pr );
9524 }
9525 */
9526 }
9527 }
9528 }
9529 }
9530 if (pMousePoint) { // clear all about the dragged point
9531 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9532 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9533 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9534 // Hide mark properties dialog if open on the replaced point
9535 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9536 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9537 g_pMarkInfoDialog->Hide();
9538
9539 delete m_pRoutePointEditTarget;
9540 m_lastRoutePointEditTarget = NULL;
9541 m_pRoutePointEditTarget = NULL;
9542 undo->AfterUndoableAction(pMousePoint);
9543 undo->InvalidateUndo();
9544 }
9545 }
9546 }
9547
9548 else if (m_bMarkEditing) { // End of way point drag
9549 if (m_pRoutePointEditTarget)
9550 if (m_bRoutePoinDragging) {
9551 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9552 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9553 }
9554 }
9555
9556 if (m_pRoutePointEditTarget)
9557 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9558
9559 if (!m_pRoutePointEditTarget) {
9560 delete m_pEditRouteArray;
9561 m_pEditRouteArray = NULL;
9562 m_bRouteEditing = false;
9563 }
9564 m_bRoutePoinDragging = false;
9565
9566 if (appending) { // Appending to the route of which the last point is
9567 // dragged onto another route
9568
9569 // copy tail from connect until length to end of current after dragging
9570
9571 int length = tail->GetnPoints();
9572 for (int i = connect + 1; i <= length; i++) {
9573 current->AddPointAndSegment(tail->GetPoint(i), false);
9574 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9575 m_routeState++;
9576 gFrame->RefreshAllCanvas();
9577 ret = true;
9578 }
9579 current->FinalizeForRendering();
9580 current->m_bIsBeingEdited = false;
9581 FinishRoute();
9582 g_pRouteMan->DeleteRoute(tail);
9583 }
9584 if (inserting) {
9585 pSelect->DeleteAllSelectableRoutePoints(current);
9586 pSelect->DeleteAllSelectableRouteSegments(current);
9587 for (int i = 1; i < connect; i++) { // numbering in the tail route
9588 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9589 }
9590 pSelect->AddAllSelectableRouteSegments(current);
9591 pSelect->AddAllSelectableRoutePoints(current);
9592 current->FinalizeForRendering();
9593 current->m_bIsBeingEdited = false;
9594 g_pRouteMan->DeleteRoute(tail);
9595 }
9596
9597 // Update the RouteProperties Dialog, if currently shown
9598 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9599 if (m_pEditRouteArray) {
9600 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9601 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9602 if (g_pRouteMan->IsRouteValid(pr)) {
9603 if (pRoutePropDialog->GetRoute() == pr) {
9604 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9605 }
9606 }
9607 }
9608 }
9609 }
9610
9611 } // g_btouch
9612
9613 else { // !g_btouch
9614 if (m_bRouteEditing) { // End of RoutePoint drag
9615 Route *tail = 0;
9616 Route *current = 0;
9617 bool appending = false;
9618 bool inserting = false;
9619 int connect = 0;
9620 int index_last;
9621 if (m_pRoutePointEditTarget) {
9622 m_pRoutePointEditTarget->m_bBlink = false;
9623 // Check to see if there is a nearby point which may replace the
9624 // dragged one
9625 RoutePoint *pMousePoint = NULL;
9626 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9627 double nearby_radius_meters =
9628 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9629 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9630 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9631 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9632 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9633 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9634 bool duplicate = false; // don't create duplicate point in routes
9635 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9636 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9637 ir++) {
9638 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9639 if (pr && pr->pRoutePointList) {
9640 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9641 wxNOT_FOUND) {
9642 duplicate = true;
9643 break;
9644 }
9645 }
9646 }
9647 }
9648
9649 // Special case:
9650 // Allow "re-use" of a route's waypoints iff it is a simple
9651 // isolated route. This allows, for instance, creation of a closed
9652 // polygon route
9653 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9654
9655 if (!duplicate) {
9656 int dlg_return;
9657 dlg_return =
9658 OCPNMessageBox(this,
9659 _("Replace this RoutePoint by the nearby "
9660 "Waypoint?"),
9661 _("OpenCPN RoutePoint change"),
9662 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9663 if (dlg_return == wxID_YES) {
9664 /*double confirmation if the dragged point has been manually
9665 * created which can be important and could be deleted
9666 * unintentionally*/
9667 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9668 pNearbyPoint);
9669 current =
9670 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9671
9672 if (tail && current && (tail != current)) {
9673 int dlg_return1;
9674 connect = tail->GetIndexOf(pNearbyPoint);
9675 int index_current_route =
9676 current->GetIndexOf(m_pRoutePointEditTarget);
9677 index_last = current->GetIndexOf(current->GetLastPoint());
9678 dlg_return1 = wxID_NO;
9679 if (index_last ==
9680 index_current_route) { // we are dragging the last
9681 // point of the route
9682 if (connect != tail->GetnPoints()) { // anything to do?
9683
9684 wxString dmsg(
9685 _("Last part of route to be appended to dragged "
9686 "route?"));
9687 if (connect == 1)
9688 dmsg =
9689 _("Full route to be appended to dragged route?");
9690
9691 dlg_return1 = OCPNMessageBox(
9692 this, dmsg, _("OpenCPN Route Create"),
9693 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9694 if (dlg_return1 == wxID_YES) {
9695 appending = true;
9696 }
9697 }
9698 } else if (index_current_route ==
9699 1) { // dragging the first point of the route
9700 if (connect != 1) { // anything to do?
9701
9702 wxString dmsg(
9703 _("First part of route to be inserted into dragged "
9704 "route?"));
9705 if (connect == tail->GetnPoints())
9706 dmsg = _(
9707 "Full route to be inserted into dragged route?");
9708
9709 dlg_return1 = OCPNMessageBox(
9710 this, dmsg, _("OpenCPN Route Create"),
9711 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9712 if (dlg_return1 == wxID_YES) {
9713 inserting = true;
9714 }
9715 }
9716 }
9717 }
9718
9719 if (m_pRoutePointEditTarget->IsShared()) {
9720 dlg_return = wxID_NO;
9721 dlg_return = OCPNMessageBox(
9722 this,
9723 _("Do you really want to delete and replace this "
9724 "WayPoint") +
9725 _T("\n") + _("which has been created manually?"),
9726 ("OpenCPN RoutePoint warning"),
9727 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9728 }
9729 }
9730 if (dlg_return == wxID_YES) {
9731 pMousePoint = pNearbyPoint;
9732 if (pMousePoint->m_bIsolatedMark) {
9733 pMousePoint->SetShared(true);
9734 }
9735 pMousePoint->m_bIsolatedMark =
9736 false; // definitely no longer isolated
9737 pMousePoint->m_bIsInRoute = true;
9738 }
9739 }
9740 }
9741 }
9742 if (!pMousePoint)
9743 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9744
9745 if (m_pEditRouteArray) {
9746 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9747 ir++) {
9748 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9749 if (g_pRouteMan->IsRouteValid(pr)) {
9750 if (pMousePoint) { // replace dragged point by nearby one
9751 int nRP =
9752 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9753
9754 pSelect->DeleteAllSelectableRoutePoints(pr);
9755 pSelect->DeleteAllSelectableRouteSegments(pr);
9756
9757 pr->pRoutePointList->Insert(nRP, pMousePoint);
9758 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9759
9760 pSelect->AddAllSelectableRouteSegments(pr);
9761 pSelect->AddAllSelectableRoutePoints(pr);
9762 }
9763 pr->FinalizeForRendering();
9764 pr->UpdateSegmentDistances();
9765 pr->m_bIsBeingEdited = false;
9766
9767 if (m_bRoutePoinDragging) {
9768 // Special case optimization.
9769 // Dragging a single point of a route
9770 // without any point additions or re-ordering
9771 if (!pMousePoint)
9772 NavObj_dB::GetInstance().UpdateRoutePoint(
9773 m_pRoutePointEditTarget);
9774 else
9775 NavObj_dB::GetInstance().UpdateRoute(pr);
9776 }
9777 pr->SetHiLite(0);
9778 }
9779 }
9780 Refresh(false);
9781 }
9782
9783 if (appending) {
9784 // copy tail from connect until length to end of current after
9785 // dragging
9786
9787 int length = tail->GetnPoints();
9788 for (int i = connect + 1; i <= length; i++) {
9789 current->AddPointAndSegment(tail->GetPoint(i), false);
9790 if (current)
9791 current->m_lastMousePointIndex = current->GetnPoints();
9792 m_routeState++;
9793 gFrame->RefreshAllCanvas();
9794 ret = true;
9795 }
9796 current->FinalizeForRendering();
9797 current->m_bIsBeingEdited = false;
9798 FinishRoute();
9799 g_pRouteMan->DeleteRoute(tail);
9800 }
9801 if (inserting) {
9802 pSelect->DeleteAllSelectableRoutePoints(current);
9803 pSelect->DeleteAllSelectableRouteSegments(current);
9804 for (int i = 1; i < connect; i++) { // numbering in the tail route
9805 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9806 }
9807 pSelect->AddAllSelectableRouteSegments(current);
9808 pSelect->AddAllSelectableRoutePoints(current);
9809 current->FinalizeForRendering();
9810 current->m_bIsBeingEdited = false;
9811 g_pRouteMan->DeleteRoute(tail);
9812 }
9813
9814 // Update the RouteProperties Dialog, if currently shown
9815 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9816 if (m_pEditRouteArray) {
9817 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9818 ir++) {
9819 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9820 if (g_pRouteMan->IsRouteValid(pr)) {
9821 if (pRoutePropDialog->GetRoute() == pr) {
9822 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9823 }
9824 }
9825 }
9826 }
9827 }
9828
9829 if (pMousePoint) {
9830 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9831 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9832 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9833 // Hide mark properties dialog if open on the replaced point
9834 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9835 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9836 g_pMarkInfoDialog->Hide();
9837
9838 delete m_pRoutePointEditTarget;
9839 m_lastRoutePointEditTarget = NULL;
9840 undo->AfterUndoableAction(pMousePoint);
9841 undo->InvalidateUndo();
9842 } else {
9843 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9844 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9845
9846 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9847 }
9848
9849 delete m_pEditRouteArray;
9850 m_pEditRouteArray = NULL;
9851 }
9852
9853 InvalidateGL();
9854 m_bRouteEditing = false;
9855 m_pRoutePointEditTarget = NULL;
9856
9857 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9858 ret = true;
9859 }
9860
9861 else if (m_bMarkEditing) { // end of Waypoint drag
9862 if (m_pRoutePointEditTarget) {
9863 if (m_bRoutePoinDragging) {
9864 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9865 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9866 }
9867 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9868 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9869 if (!g_bopengl) {
9870 wxRect wp_rect;
9871 RoutePointGui(*m_pRoutePointEditTarget)
9872 .CalculateDCRect(m_dc_route, this, &wp_rect);
9873 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9874 RefreshRect(wp_rect, true);
9875 }
9876 }
9877 m_pRoutePointEditTarget = NULL;
9878 m_bMarkEditing = false;
9879 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9880 ret = true;
9881 }
9882
9883 else if (leftIsDown) { // left click for chart center
9884 leftIsDown = false;
9885 ret = false;
9886
9887 if (!g_btouch) {
9888 if (!m_bChartDragging && !m_bMeasure_Active) {
9889 } else {
9890 m_bChartDragging = false;
9891 }
9892 }
9893 }
9894 m_bRoutePoinDragging = false;
9895 } // !btouch
9896
9897 if (ret) return true;
9898 } // left up
9899
9900 if (event.RightDown()) {
9901 SetFocus(); // This is to let a plugin know which canvas is right-clicked
9902 last_drag.x = mx;
9903 last_drag.y = my;
9904
9905 if (g_btouch) {
9906 // if( m_pRoutePointEditTarget )
9907 // return false;
9908 }
9909
9910 ret = true;
9911 m_FinishRouteOnKillFocus = false;
9912 CallPopupMenu(mx, my);
9913 m_FinishRouteOnKillFocus = true;
9914 } // Right down
9915
9916 return ret;
9917}
9918
9919bool panleftIsDown;
9920
9921bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
9922 // Skip all mouse processing if shift is held.
9923 // This allows plugins to implement shift+drag behaviors.
9924 if (event.ShiftDown()) {
9925 return false;
9926 }
9927 int x, y;
9928 event.GetPosition(&x, &y);
9929
9930 x *= m_displayScale;
9931 y *= m_displayScale;
9932
9933 // Check for wheel rotation
9934 // ideally, should be just longer than the time between
9935 // processing accumulated mouse events from the event queue
9936 // as would happen during screen redraws.
9937 int wheel_dir = event.GetWheelRotation();
9938
9939 if (wheel_dir) {
9940 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
9941 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
9942
9943 double factor = g_mouse_zoom_sensitivity;
9944 if (wheel_dir < 0) factor = 1 / factor;
9945
9946 if (g_bsmoothpanzoom) {
9947 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
9948 if (wheel_dir == m_last_wheel_dir) {
9949 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
9950 // m_zoom_target /= factor;
9951 } else
9952 StopMovement();
9953 } else {
9954 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
9955 m_wheelstopwatch.Start(0);
9956 // m_zoom_target = VPoint.chart_scale / factor;
9957 }
9958 }
9959
9960 m_last_wheel_dir = wheel_dir;
9961
9962 ZoomCanvas(factor, true, false);
9963 }
9964
9965 if (event.LeftDown()) {
9966 // Skip the first left click if it will cause a canvas focus shift
9967 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
9968 // printf("focus shift\n");
9969 return false;
9970 }
9971
9972 last_drag.x = x, last_drag.y = y;
9973 panleftIsDown = true;
9974 }
9975
9976 if (event.LeftUp()) {
9977 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
9978 // seen here.
9979 panleftIsDown = false;
9980
9981 if (!g_btouch) {
9982 if (!m_bChartDragging && !m_bMeasure_Active) {
9983 switch (cursor_region) {
9984 case MID_RIGHT: {
9985 PanCanvas(100, 0);
9986 break;
9987 }
9988
9989 case MID_LEFT: {
9990 PanCanvas(-100, 0);
9991 break;
9992 }
9993
9994 case MID_TOP: {
9995 PanCanvas(0, 100);
9996 break;
9997 }
9998
9999 case MID_BOT: {
10000 PanCanvas(0, -100);
10001 break;
10002 }
10003
10004 case CENTER: {
10005 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10006 break;
10007 }
10008 }
10009 } else {
10010 m_bChartDragging = false;
10011 }
10012 }
10013 }
10014 }
10015
10016 if (event.Dragging() && event.LeftIsDown()) {
10017 /*
10018 * fixed dragging.
10019 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10020 * before the drag event. Hence, as there is no mouse down event, last_drag
10021 * is not reset before the drag. And that results in one single drag
10022 * session, meaning you cannot drag the map a few miles north, lift your
10023 * finger, and the go even further north. Instead, the map resets itself
10024 * always to the very first drag start (since there is not reset of
10025 * last_drag).
10026 *
10027 * Besides, should not left down and dragging be enough of a situation to
10028 * start a drag procedure?
10029 *
10030 * Anyways, guarded it to be active in touch situations only.
10031 */
10032
10033 if (g_btouch) {
10034 struct timespec now;
10035 clock_gettime(CLOCK_MONOTONIC, &now);
10036 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10037
10038 if (false == m_bChartDragging) {
10039 // Reset drag calculation members
10040 last_drag.x = x, last_drag.y = y;
10041 m_bChartDragging = true;
10042 m_chart_drag_total_time = 0;
10043 m_chart_drag_total_x = 0;
10044 m_chart_drag_total_y = 0;
10045 m_inertia_last_drag_x = x;
10046 m_inertia_last_drag_y = y;
10047 m_drag_vec_x.clear();
10048 m_drag_vec_y.clear();
10049 m_drag_vec_t.clear();
10050 m_last_drag_time = tnow;
10051 }
10052
10053 // Calculate and store drag dynamics.
10054 uint64_t delta_t = tnow - m_last_drag_time;
10055 double delta_tf = delta_t / 1e9;
10056
10057 m_chart_drag_total_time += delta_tf;
10058 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10059 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10060
10061 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10062 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10063 m_drag_vec_t.push_back(delta_tf);
10064
10065 m_inertia_last_drag_x = x;
10066 m_inertia_last_drag_y = y;
10067 m_last_drag_time = tnow;
10068
10069 if ((last_drag.x != x) || (last_drag.y != y)) {
10070 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10071 // dragging on route create.
10072 // github #2994
10073 m_bChartDragging = true;
10074 StartTimedMovement();
10075 m_pan_drag.x += last_drag.x - x;
10076 m_pan_drag.y += last_drag.y - y;
10077 last_drag.x = x, last_drag.y = y;
10078 }
10079 }
10080 } else {
10081 if ((last_drag.x != x) || (last_drag.y != y)) {
10082 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10083 // dragging on route create.
10084 // github #2994
10085 m_bChartDragging = true;
10086 StartTimedMovement();
10087 m_pan_drag.x += last_drag.x - x;
10088 m_pan_drag.y += last_drag.y - y;
10089 last_drag.x = x, last_drag.y = y;
10090 }
10091 }
10092 }
10093
10094 // Handle some special cases
10095 if (g_btouch) {
10096 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10097 // deactivate next LeftUp to ovoid creating an unexpected point
10098 m_DoubleClickTimer->Start();
10099 singleClickEventIsValid = false;
10100 }
10101 }
10102 }
10103
10104 return true;
10105}
10106
10107void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10108 if (MouseEventOverlayWindows(event)) return;
10109
10110 if (MouseEventSetup(event)) return; // handled, no further action required
10111
10112 if (!MouseEventProcessObjects(event)) MouseEventProcessCanvas(event);
10113}
10114
10115void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10116 // Switch to the appropriate cursor on mouse movement
10117
10118 wxCursor *ptarget_cursor = pCursorArrow;
10119 if (!pPlugIn_Cursor) {
10120 ptarget_cursor = pCursorArrow;
10121 if ((!m_routeState) &&
10122 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10123 if (cursor_region == MID_RIGHT) {
10124 ptarget_cursor = pCursorRight;
10125 } else if (cursor_region == MID_LEFT) {
10126 ptarget_cursor = pCursorLeft;
10127 } else if (cursor_region == MID_TOP) {
10128 ptarget_cursor = pCursorDown;
10129 } else if (cursor_region == MID_BOT) {
10130 ptarget_cursor = pCursorUp;
10131 } else {
10132 ptarget_cursor = pCursorArrow;
10133 }
10134 } else if (m_bMeasure_Active ||
10135 m_routeState) // If Measure tool use Pencil Cursor
10136 ptarget_cursor = pCursorPencil;
10137 } else {
10138 ptarget_cursor = pPlugIn_Cursor;
10139 }
10140
10141 SetCursor(*ptarget_cursor);
10142}
10143
10144void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10145 SetCursor(*pCursorArrow);
10146}
10147
10148void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10149 ChartPlugInWrapper *target_plugin_chart = NULL;
10150 s57chart *Chs57 = NULL;
10151 wxFileName file;
10152 wxArrayString files;
10153
10154 ChartBase *target_chart = GetChartAtCursor();
10155 if (target_chart) {
10156 file.Assign(target_chart->GetFullPath());
10157 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10158 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10159 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10160 else
10161 Chs57 = dynamic_cast<s57chart *>(target_chart);
10162 } else { // target_chart = null, might be mbtiles
10163 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10164 unsigned int im = stackIndexArray.size();
10165 int scale = 2147483647; // max 32b integer
10166 if (VPoint.b_quilt && im > 0) {
10167 for (unsigned int is = 0; is < im; is++) {
10168 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10169 CHART_TYPE_MBTILES) {
10170 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10171 double lat, lon;
10172 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10173 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10174 .GetBBox()
10175 .Contains(lat, lon)) {
10176 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10177 scale) {
10178 scale =
10179 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10180 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10181 }
10182 }
10183 }
10184 }
10185 }
10186 }
10187
10188 std::vector<Ais8_001_22 *> area_notices;
10189
10190 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10191 float vp_scale = GetVPScale();
10192
10193 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10194 auto target_data = target.second;
10195 if (!target_data->area_notices.empty()) {
10196 for (auto &ani : target_data->area_notices) {
10197 Ais8_001_22 &area_notice = ani.second;
10198
10199 BoundingBox bbox;
10200
10201 for (Ais8_001_22_SubAreaList::iterator sa =
10202 area_notice.sub_areas.begin();
10203 sa != area_notice.sub_areas.end(); ++sa) {
10204 switch (sa->shape) {
10205 case AIS8_001_22_SHAPE_CIRCLE: {
10206 wxPoint target_point;
10207 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10208 bbox.Expand(target_point);
10209 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10210 break;
10211 }
10212 case AIS8_001_22_SHAPE_RECT: {
10213 wxPoint target_point;
10214 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10215 bbox.Expand(target_point);
10216 if (sa->e_dim_m > sa->n_dim_m)
10217 bbox.EnLarge(sa->e_dim_m * vp_scale);
10218 else
10219 bbox.EnLarge(sa->n_dim_m * vp_scale);
10220 break;
10221 }
10222 case AIS8_001_22_SHAPE_POLYGON:
10223 case AIS8_001_22_SHAPE_POLYLINE: {
10224 for (int i = 0; i < 4; ++i) {
10225 double lat = sa->latitude;
10226 double lon = sa->longitude;
10227 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10228 &lat, &lon);
10229 wxPoint target_point;
10230 GetCanvasPointPix(lat, lon, &target_point);
10231 bbox.Expand(target_point);
10232 }
10233 break;
10234 }
10235 case AIS8_001_22_SHAPE_SECTOR: {
10236 double lat1 = sa->latitude;
10237 double lon1 = sa->longitude;
10238 double lat, lon;
10239 wxPoint target_point;
10240 GetCanvasPointPix(lat1, lon1, &target_point);
10241 bbox.Expand(target_point);
10242 for (int i = 0; i < 18; ++i) {
10243 ll_gc_ll(
10244 lat1, lon1,
10245 sa->left_bound_deg +
10246 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10247 sa->radius_m / 1852.0, &lat, &lon);
10248 GetCanvasPointPix(lat, lon, &target_point);
10249 bbox.Expand(target_point);
10250 }
10251 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10252 &lat, &lon);
10253 GetCanvasPointPix(lat, lon, &target_point);
10254 bbox.Expand(target_point);
10255 break;
10256 }
10257 }
10258 }
10259
10260 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10261 area_notices.push_back(&area_notice);
10262 }
10263 }
10264 }
10265 }
10266 }
10267
10268 if (target_chart || !area_notices.empty() || file.HasName()) {
10269 // Go get the array of all objects at the cursor lat/lon
10270 int sel_rad_pix = 5;
10271 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10272
10273 // Make sure we always get the lights from an object, even if we are
10274 // currently not displaying lights on the chart.
10275
10276 SetCursor(wxCURSOR_WAIT);
10277 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10278 if (!lightsVis) SetShowENCLights(true);
10279 ;
10280
10281 ListOfObjRazRules *rule_list = NULL;
10282 ListOfPI_S57Obj *pi_rule_list = NULL;
10283 if (Chs57)
10284 rule_list =
10285 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10286 else if (target_plugin_chart)
10287 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10288 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10289
10290 ListOfObjRazRules *overlay_rule_list = NULL;
10291 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10292 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10293
10294 if (CHs57_Overlay) {
10295 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10296 zlat, zlon, SelectRadius, &GetVP());
10297 }
10298
10299 if (!lightsVis) SetShowENCLights(false);
10300
10301 wxString objText;
10302 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10303 wxString face = dFont->GetFaceName();
10304
10305 if (NULL == g_pObjectQueryDialog) {
10306 g_pObjectQueryDialog =
10307 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10308 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10309 }
10310
10311 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10312 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10313
10314#ifdef __WXOSX__
10315 // Auto Adjustment for dark mode
10316 fg = g_pObjectQueryDialog->GetForegroundColour();
10317#endif
10318
10319 objText.Printf(
10320 _T("<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>"),
10321 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10322
10323#ifdef __WXOSX__
10324 int points = dFont->GetPointSize();
10325#else
10326 int points = dFont->GetPointSize() + 1;
10327#endif
10328
10329 int sizes[7];
10330 for (int i = -2; i < 5; i++) {
10331 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10332 }
10333 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10334
10335 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += _T("<i>");
10336
10337 if (overlay_rule_list && CHs57_Overlay) {
10338 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10339 objText << _T("<hr noshade>");
10340 }
10341
10342 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10343 an != area_notices.end(); ++an) {
10344 objText << _T( "<b>AIS Area Notice:</b> " );
10345 objText << ais8_001_22_notice_names[(*an)->notice_type];
10346 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10347 (*an)->sub_areas.begin();
10348 sa != (*an)->sub_areas.end(); ++sa)
10349 if (!sa->text.empty()) objText << sa->text;
10350 objText << _T( "<br>expires: " ) << (*an)->expiry_time.Format();
10351 objText << _T( "<hr noshade>" );
10352 }
10353
10354 if (Chs57)
10355 objText << Chs57->CreateObjDescriptions(rule_list);
10356 else if (target_plugin_chart)
10357 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10358 pi_rule_list);
10359
10360 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << _T("</i>");
10361
10362 // Add the additional info files
10363 wxString AddFiles, filenameOK;
10364 int filecount = 0;
10365 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10366 // plugin
10367
10368 AddFiles = wxString::Format(
10369 _T("<hr noshade><br><b>Additional info files attached to: </b> ")
10370 _T("<font ")
10371 _T("size=-2>%s</font><br><table border=0 cellspacing=0 ")
10372 _T("cellpadding=3>"),
10373 file.GetFullName());
10374 file.Normalize();
10375 file.Assign(file.GetPath(), wxT(""));
10376 wxDir dir(file.GetFullPath());
10377 wxString filename;
10378 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10379 while (cont) {
10380 file.Assign(dir.GetNameWithSep().append(filename));
10381 wxString FormatString =
10382 _T("<td valign=top><font size=-2><a ")
10383 _T("href=\"%s\">%s</a></font></td>");
10384 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10385 filenameOK = file.GetFullPath(); // remember last valid name
10386 // we are making a 3 columns table. New row only every third file
10387 if (3 * ((int)filecount / 3) == filecount)
10388 FormatString.Prepend(_T("<tr>")); // new row
10389 else
10390 FormatString.Prepend(
10391 _T("<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>")); // an empty
10392 // spacer column
10393
10394 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10395 file.GetFullName());
10396 filecount++;
10397 }
10398 cont = dir.GetNext(&filename);
10399 }
10400 objText << AddFiles << _T("</table>");
10401 }
10402 objText << _T("</font>");
10403 objText << _T("</body></html>");
10404
10405 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10406 g_pObjectQueryDialog->SetHTMLPage(objText);
10407 g_pObjectQueryDialog->Show();
10408 }
10409 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10410 // generate an event to avoid double code
10411 wxHtmlLinkInfo hli(filenameOK);
10412 wxHtmlLinkEvent hle(1, hli);
10413 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10414 }
10415
10416 if (rule_list) rule_list->Clear();
10417 delete rule_list;
10418
10419 if (overlay_rule_list) overlay_rule_list->Clear();
10420 delete overlay_rule_list;
10421
10422 if (pi_rule_list) pi_rule_list->Clear();
10423 delete pi_rule_list;
10424
10425 SetCursor(wxCURSOR_ARROW);
10426 }
10427}
10428
10429void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10430 bool bNew = false;
10431 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10432 // Dialog
10433 g_pMarkInfoDialog = new MarkInfoDlg(this);
10434 bNew = true;
10435 }
10436
10437 if (1 /*g_bresponsive*/) {
10438 wxSize canvas_size = GetSize();
10439
10440 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10441 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10442
10443 g_pMarkInfoDialog->Layout();
10444
10445 wxPoint canvas_pos = GetPosition();
10446 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10447
10448 bool newFit = false;
10449 if (canvas_size.x < fitted_size.x) {
10450 fitted_size.x = canvas_size.x - 40;
10451 if (canvas_size.y < fitted_size.y)
10452 fitted_size.y -= 40; // scrollbar added
10453 }
10454 if (canvas_size.y < fitted_size.y) {
10455 fitted_size.y = canvas_size.y - 40;
10456 if (canvas_size.x < fitted_size.x)
10457 fitted_size.x -= 40; // scrollbar added
10458 }
10459
10460 if (newFit) {
10461 g_pMarkInfoDialog->SetSize(fitted_size);
10462 g_pMarkInfoDialog->Centre();
10463 }
10464 }
10465
10466 markPoint->m_bRPIsBeingEdited = false;
10467
10468 wxString title_base = _("Mark Properties");
10469 if (markPoint->m_bIsInRoute) {
10470 title_base = _("Waypoint Properties");
10471 }
10472 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10473 g_pMarkInfoDialog->UpdateProperties();
10474 if (markPoint->m_bIsInLayer) {
10475 wxString caption(wxString::Format(_T("%s, %s: %s"), title_base, _("Layer"),
10476 GetLayerName(markPoint->m_LayerID)));
10477 g_pMarkInfoDialog->SetDialogTitle(caption);
10478 } else
10479 g_pMarkInfoDialog->SetDialogTitle(title_base);
10480
10481 g_pMarkInfoDialog->Show();
10482 g_pMarkInfoDialog->Raise();
10483 g_pMarkInfoDialog->InitialFocus();
10484 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10485}
10486
10487void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10488 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10489 pRoutePropDialog->SetRouteAndUpdate(selected);
10490 // pNew->UpdateProperties();
10491 pRoutePropDialog->Show();
10492 pRoutePropDialog->Raise();
10493 return;
10494 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10495 this); // There is one global instance of the RouteProp Dialog
10496
10497 if (g_bresponsive) {
10498 wxSize canvas_size = GetSize();
10499 wxPoint canvas_pos = GetPosition();
10500 wxSize fitted_size = pRoutePropDialog->GetSize();
10501 ;
10502
10503 if (canvas_size.x < fitted_size.x) {
10504 fitted_size.x = canvas_size.x;
10505 if (canvas_size.y < fitted_size.y)
10506 fitted_size.y -= 20; // scrollbar added
10507 }
10508 if (canvas_size.y < fitted_size.y) {
10509 fitted_size.y = canvas_size.y;
10510 if (canvas_size.x < fitted_size.x)
10511 fitted_size.x -= 20; // scrollbar added
10512 }
10513
10514 pRoutePropDialog->SetSize(fitted_size);
10515 pRoutePropDialog->Centre();
10516
10517 // int xp = (canvas_size.x - fitted_size.x)/2;
10518 // int yp = (canvas_size.y - fitted_size.y)/2;
10519
10520 wxPoint xxp = ClientToScreen(canvas_pos);
10521 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10522 }
10523
10524 pRoutePropDialog->SetRouteAndUpdate(selected);
10525
10526 pRoutePropDialog->Show();
10527
10528 Refresh(false);
10529}
10530
10531void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10532 pTrackPropDialog = TrackPropDlg::getInstance(
10533 this); // There is one global instance of the RouteProp Dialog
10534
10535 pTrackPropDialog->SetTrackAndUpdate(selected);
10536 pTrackPropDialog->UpdateProperties();
10537
10538 pTrackPropDialog->Show();
10539
10540 Refresh(false);
10541}
10542
10543void pupHandler_PasteWaypoint() {
10544 Kml kml;
10545
10546 int pasteBuffer = kml.ParsePasteBuffer();
10547 RoutePoint *pasted = kml.GetParsedRoutePoint();
10548 if (!pasted) return;
10549
10550 double nearby_radius_meters =
10551 g_Platform->GetSelectRadiusPix() /
10552 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10553
10554 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10555 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10556
10557 int answer = wxID_NO;
10558 if (nearPoint && !nearPoint->m_bIsInLayer) {
10559 wxString msg;
10560 msg << _(
10561 "There is an existing waypoint at the same location as the one you are "
10562 "pasting. Would you like to merge the pasted data with it?\n\n");
10563 msg << _("Answering 'No' will create a new waypoint at the same location.");
10564 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10565 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10566 }
10567
10568 if (answer == wxID_YES) {
10569 nearPoint->SetName(pasted->GetName());
10570 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10571 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10572 pRouteManagerDialog->UpdateWptListCtrl();
10573 }
10574
10575 if (answer == wxID_NO) {
10576 RoutePoint *newPoint = new RoutePoint(pasted);
10577 newPoint->m_bIsolatedMark = true;
10578 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10579 newPoint);
10580 // pConfig->AddNewWayPoint(newPoint, -1);
10581 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10582
10583 pWayPointMan->AddRoutePoint(newPoint);
10584 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10585 pRouteManagerDialog->UpdateWptListCtrl();
10586 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10587 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10588 }
10589
10590 gFrame->InvalidateAllGL();
10591 gFrame->RefreshAllCanvas(false);
10592}
10593
10594void pupHandler_PasteRoute() {
10595 Kml kml;
10596
10597 int pasteBuffer = kml.ParsePasteBuffer();
10598 Route *pasted = kml.GetParsedRoute();
10599 if (!pasted) return;
10600
10601 double nearby_radius_meters =
10602 g_Platform->GetSelectRadiusPix() /
10603 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10604
10605 RoutePoint *curPoint;
10606 RoutePoint *nearPoint;
10607 RoutePoint *prevPoint = NULL;
10608
10609 bool mergepoints = false;
10610 bool createNewRoute = true;
10611 int existingWaypointCounter = 0;
10612
10613 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10614 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10615 nearPoint = pWayPointMan->GetNearbyWaypoint(
10616 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10617 if (nearPoint) {
10618 mergepoints = true;
10619 existingWaypointCounter++;
10620 // Small hack here to avoid both extending RoutePoint and repeating all
10621 // the GetNearbyWaypoint calculations. Use existin data field in
10622 // RoutePoint as temporary storage.
10623 curPoint->m_bPtIsSelected = true;
10624 }
10625 }
10626
10627 int answer = wxID_NO;
10628 if (mergepoints) {
10629 wxString msg;
10630 msg << _(
10631 "There are existing waypoints at the same location as some of the ones "
10632 "you are pasting. Would you like to just merge the pasted data into "
10633 "them?\n\n");
10634 msg << _("Answering 'No' will create all new waypoints for this route.");
10635 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10636 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10637
10638 if (answer == wxID_CANCEL) {
10639 return;
10640 }
10641 }
10642
10643 // If all waypoints exist since before, and a route with the same name, we
10644 // don't create a new route.
10645 if (mergepoints && answer == wxID_YES &&
10646 existingWaypointCounter == pasted->GetnPoints()) {
10647 wxRouteListNode *route_node = pRouteList->GetFirst();
10648 while (route_node) {
10649 Route *proute = route_node->GetData();
10650
10651 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10652 createNewRoute = false;
10653 break;
10654 }
10655 route_node = route_node->GetNext();
10656 }
10657 }
10658
10659 Route *newRoute = 0;
10660 RoutePoint *newPoint = 0;
10661
10662 if (createNewRoute) {
10663 newRoute = new Route();
10664 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10665 }
10666
10667 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10668 curPoint = pasted->GetPoint(i);
10669 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10670 curPoint->m_bPtIsSelected = false;
10671 newPoint = pWayPointMan->GetNearbyWaypoint(
10672 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10673 newPoint->SetName(curPoint->GetName());
10674 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10675
10676 if (createNewRoute) newRoute->AddPoint(newPoint);
10677 } else {
10678 curPoint->m_bPtIsSelected = false;
10679
10680 newPoint = new RoutePoint(curPoint);
10681 newPoint->m_bIsolatedMark = false;
10682 newPoint->SetIconName(_T("circle"));
10683 newPoint->m_bIsVisible = true;
10684 newPoint->m_bShowName = false;
10685 newPoint->SetShared(false);
10686
10687 newRoute->AddPoint(newPoint);
10688 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10689 newPoint);
10690 // pConfig->AddNewWayPoint(newPoint, -1);
10691 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10692 pWayPointMan->AddRoutePoint(newPoint);
10693 }
10694 if (i > 1 && createNewRoute)
10695 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10696 curPoint->m_lat, curPoint->m_lon,
10697 prevPoint, newPoint, newRoute);
10698 prevPoint = newPoint;
10699 }
10700
10701 if (createNewRoute) {
10702 pRouteList->Append(newRoute);
10703 // pConfig->AddNewRoute(newRoute); // use auto next num
10704 NavObj_dB::GetInstance().InsertRoute(newRoute);
10705
10706 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10707 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10708 }
10709
10710 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10711 pRouteManagerDialog->UpdateRouteListCtrl();
10712 pRouteManagerDialog->UpdateWptListCtrl();
10713 }
10714 gFrame->InvalidateAllGL();
10715 gFrame->RefreshAllCanvas(false);
10716 }
10717 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10718 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10719}
10720
10721void pupHandler_PasteTrack() {
10722 Kml kml;
10723
10724 int pasteBuffer = kml.ParsePasteBuffer();
10725 Track *pasted = kml.GetParsedTrack();
10726 if (!pasted) return;
10727
10728 TrackPoint *curPoint;
10729
10730 Track *newTrack = new Track();
10731 TrackPoint *newPoint;
10732 TrackPoint *prevPoint = NULL;
10733
10734 newTrack->SetName(pasted->GetName());
10735
10736 for (int i = 0; i < pasted->GetnPoints(); i++) {
10737 curPoint = pasted->GetPoint(i);
10738
10739 newPoint = new TrackPoint(curPoint);
10740
10741 wxDateTime now = wxDateTime::Now();
10742 newPoint->SetCreateTime(curPoint->GetCreateTime());
10743
10744 newTrack->AddPoint(newPoint);
10745
10746 if (prevPoint)
10747 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10748 newPoint->m_lat, newPoint->m_lon,
10749 prevPoint, newPoint, newTrack);
10750
10751 prevPoint = newPoint;
10752 }
10753
10754 g_TrackList.push_back(newTrack);
10755 // pConfig->AddNewTrack(newTrack);
10756 NavObj_dB::GetInstance().InsertTrack(newTrack);
10757
10758 gFrame->InvalidateAllGL();
10759 gFrame->RefreshAllCanvas(false);
10760}
10761
10762bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
10763 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
10764 m_pFoundRoutePoint, m_FoundAIS_MMSI,
10765 m_pIDXCandidate, m_nmea_log);
10766
10767 Connect(
10768 wxEVT_COMMAND_MENU_SELECTED,
10769 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10770
10771 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
10772
10773 Disconnect(
10774 wxEVT_COMMAND_MENU_SELECTED,
10775 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10776
10777 delete m_canvasMenu;
10778 m_canvasMenu = NULL;
10779
10780#ifdef __WXQT__
10781 // gFrame->SurfaceToolbar();
10782 // g_MainToolbar->Raise();
10783#endif
10784
10785 return true;
10786}
10787
10788void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
10789 // Pass menu events from the canvas to the menu handler
10790 // This is necessarily in ChartCanvas since that is the menu's parent.
10791 if (m_canvasMenu) {
10792 m_canvasMenu->PopupMenuHandler(event);
10793 }
10794 return;
10795}
10796
10797void ChartCanvas::StartRoute(void) {
10798 // Do not allow more than one canvas to create a route at one time.
10799 if (g_brouteCreating) return;
10800
10801 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
10802
10803 g_brouteCreating = true;
10804 m_routeState = 1;
10805 m_bDrawingRoute = false;
10806 SetCursor(*pCursorPencil);
10807 // SetCanvasToolbarItemState(ID_ROUTE, true);
10808 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
10809
10810 HideGlobalToolbar();
10811
10812#ifdef __ANDROID__
10813 androidSetRouteAnnunciator(true);
10814#endif
10815}
10816
10817void ChartCanvas::FinishRoute(void) {
10818 m_routeState = 0;
10819 m_prev_pMousePoint = NULL;
10820 m_bDrawingRoute = false;
10821
10822 // SetCanvasToolbarItemState(ID_ROUTE, false);
10823 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
10824#ifdef __ANDROID__
10825 androidSetRouteAnnunciator(false);
10826#endif
10827
10828 SetCursor(*pCursorArrow);
10829
10830 if (m_pMouseRoute) {
10831 if (m_bAppendingRoute) {
10832 // pConfig->UpdateRoute(m_pMouseRoute);
10833 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
10834 } else {
10835 if (m_pMouseRoute->GetnPoints() > 1) {
10836 // pConfig->AddNewRoute(m_pMouseRoute);
10837 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
10838 } else {
10839 g_pRouteMan->DeleteRoute(m_pMouseRoute);
10840 m_pMouseRoute = NULL;
10841 }
10842 }
10843 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
10844
10845 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
10846 (pRoutePropDialog->IsShown())) {
10847 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
10848 }
10849
10850 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
10851 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10852 pRouteManagerDialog->UpdateRouteListCtrl();
10853 }
10854 }
10855 m_bAppendingRoute = false;
10856 m_pMouseRoute = NULL;
10857
10858 m_pSelectedRoute = NULL;
10859
10860 undo->InvalidateUndo();
10861 gFrame->RefreshAllCanvas(true);
10862
10863 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
10864
10865 ShowGlobalToolbar();
10866
10867 g_brouteCreating = false;
10868}
10869
10870void ChartCanvas::HideGlobalToolbar() {
10871 if (m_canvasIndex == 0) {
10872 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
10873 }
10874}
10875
10876void ChartCanvas::ShowGlobalToolbar() {
10877 if (m_canvasIndex == 0) {
10878 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
10879 }
10880}
10881
10882void ChartCanvas::ShowAISTargetList(void) {
10883 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
10884 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
10885 }
10886
10887 g_pAISTargetList->UpdateAISTargetList();
10888}
10889
10890void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
10891 if (!m_bShowOutlines) return;
10892
10893 if (!ChartData) return;
10894
10895 int nEntry = ChartData->GetChartTableEntries();
10896
10897 for (int i = 0; i < nEntry; i++) {
10898 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
10899
10900 // Check to see if the candidate chart is in the currently active group
10901 bool b_group_draw = false;
10902 if (m_groupIndex > 0) {
10903 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
10904 int index = pt->GetGroupArray()[ig];
10905 if (m_groupIndex == index) {
10906 b_group_draw = true;
10907 break;
10908 }
10909 }
10910 } else
10911 b_group_draw = true;
10912
10913 if (b_group_draw) RenderChartOutline(dc, i, vp);
10914 }
10915
10916 // On CM93 Composite Charts, draw the outlines of the next smaller
10917 // scale cell
10918 cm93compchart *pcm93 = NULL;
10919 if (VPoint.b_quilt) {
10920 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
10921 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
10922 pcm93 = (cm93compchart *)pch;
10923 break;
10924 }
10925 } else if (m_singleChart &&
10926 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
10927 pcm93 = (cm93compchart *)m_singleChart;
10928
10929 if (pcm93) {
10930 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
10931 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
10932
10933 if (zoom_factor > 8.0) {
10934 wxPen mPen(GetGlobalColor(_T("UINFM")), 2, wxPENSTYLE_SHORT_DASH);
10935 dc.SetPen(mPen);
10936 } else {
10937 wxPen mPen(GetGlobalColor(_T("UINFM")), 1, wxPENSTYLE_SOLID);
10938 dc.SetPen(mPen);
10939 }
10940
10941 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
10942 }
10943}
10944
10945void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
10946#ifdef ocpnUSE_GL
10947 if (g_bopengl && m_glcc) {
10948 /* opengl version specially optimized */
10949 m_glcc->RenderChartOutline(dc, dbIndex, vp);
10950 return;
10951 }
10952#endif
10953
10954 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
10955 if (!ChartData->IsChartAvailable(dbIndex)) return;
10956 }
10957
10958 float plylat, plylon;
10959 float plylat1, plylon1;
10960
10961 int pixx, pixy, pixx1, pixy1;
10962
10963 LLBBox box;
10964 ChartData->GetDBBoundingBox(dbIndex, box);
10965
10966 // Don't draw an outline in the case where the chart covers the entire world
10967 // */
10968 if (box.GetLonRange() == 360) return;
10969
10970 double lon_bias = 0;
10971 // chart is outside of viewport lat/lon bounding box
10972 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
10973
10974 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10975
10976 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
10977 dc.SetPen(wxPen(GetGlobalColor(_T ( "YELO1" )), 1, wxPENSTYLE_SOLID));
10978
10979 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
10980 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFG" )), 1, wxPENSTYLE_SOLID));
10981
10982 else
10983 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFR" )), 1, wxPENSTYLE_SOLID));
10984
10985 // Are there any aux ply entries?
10986 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
10987 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
10988 {
10989 wxPoint r, r1;
10990
10991 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10992 plylon += lon_bias;
10993
10994 GetCanvasPointPix(plylat, plylon, &r);
10995 pixx = r.x;
10996 pixy = r.y;
10997
10998 for (int i = 0; i < nPly - 1; i++) {
10999 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11000 plylon1 += lon_bias;
11001
11002 GetCanvasPointPix(plylat1, plylon1, &r1);
11003 pixx1 = r1.x;
11004 pixy1 = r1.y;
11005
11006 int pixxs1 = pixx1;
11007 int pixys1 = pixy1;
11008
11009 bool b_skip = false;
11010
11011 if (vp.chart_scale > 5e7) {
11012 // calculate projected distance between these two points in meters
11013 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11014 pow((double)(pixy1 - pixy), 2)) /
11015 vp.view_scale_ppm;
11016
11017 if (dist > 0.0) {
11018 // calculate GC distance between these two points in meters
11019 double distgc =
11020 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11021
11022 // If the distances are nonsense, it means that the scale is very
11023 // small and the segment wrapped the world So skip it....
11024 // TODO improve this to draw two segments
11025 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11026 b_skip = true;
11027 } else
11028 b_skip = true;
11029 }
11030
11031 ClipResult res = cohen_sutherland_line_clip_i(
11032 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11033 if (res != Invisible && !b_skip)
11034 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11035
11036 plylat = plylat1;
11037 plylon = plylon1;
11038 pixx = pixxs1;
11039 pixy = pixys1;
11040 }
11041
11042 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11043 plylon1 += lon_bias;
11044
11045 GetCanvasPointPix(plylat1, plylon1, &r1);
11046 pixx1 = r1.x;
11047 pixy1 = r1.y;
11048
11049 ClipResult res = cohen_sutherland_line_clip_i(
11050 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11051 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11052 }
11053
11054 else // Use Aux PlyPoints
11055 {
11056 wxPoint r, r1;
11057
11058 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11059 for (int j = 0; j < nAuxPlyEntries; j++) {
11060 int nAuxPly =
11061 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11062 GetCanvasPointPix(plylat, plylon, &r);
11063 pixx = r.x;
11064 pixy = r.y;
11065
11066 for (int i = 0; i < nAuxPly - 1; i++) {
11067 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11068
11069 GetCanvasPointPix(plylat1, plylon1, &r1);
11070 pixx1 = r1.x;
11071 pixy1 = r1.y;
11072
11073 int pixxs1 = pixx1;
11074 int pixys1 = pixy1;
11075
11076 bool b_skip = false;
11077
11078 if (vp.chart_scale > 5e7) {
11079 // calculate projected distance between these two points in meters
11080 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11081 ((pixy1 - pixy) * (pixy1 - pixy))) /
11082 vp.view_scale_ppm;
11083 if (dist > 0.0) {
11084 // calculate GC distance between these two points in meters
11085 double distgc =
11086 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11087
11088 // If the distances are nonsense, it means that the scale is very
11089 // small and the segment wrapped the world So skip it....
11090 // TODO improve this to draw two segments
11091 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11092 b_skip = true;
11093 } else
11094 b_skip = true;
11095 }
11096
11097 ClipResult res = cohen_sutherland_line_clip_i(
11098 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11099 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11100
11101 plylat = plylat1;
11102 plylon = plylon1;
11103 pixx = pixxs1;
11104 pixy = pixys1;
11105 }
11106
11107 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11108 GetCanvasPointPix(plylat1, plylon1, &r1);
11109 pixx1 = r1.x;
11110 pixy1 = r1.y;
11111
11112 ClipResult res = cohen_sutherland_line_clip_i(
11113 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11114 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11115 }
11116 }
11117}
11118
11119static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
11120 const wxString &second) {
11121 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11122
11123 int pointsize = dFont->GetPointSize();
11124 pointsize /= OCPN_GetWinDIPScaleFactor();
11125
11126 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11127 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11128 false, dFont->GetFaceName());
11129
11130 dc.SetFont(*psRLI_font);
11131
11132 int w1, h1;
11133 int w2 = 0;
11134 int h2 = 0;
11135 int h, w;
11136
11137 int xp, yp;
11138 int hilite_offset = 3;
11139#ifdef __WXMAC__
11140 wxScreenDC sdc;
11141 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
11142 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
11143#else
11144 dc.GetTextExtent(first, &w1, &h1);
11145 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
11146#endif
11147
11148 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11149 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11150
11151 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
11152 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11153
11154 h = h1 + h2;
11155
11156 xp = ref_point.x - w;
11157 yp = ref_point.y;
11158 yp += hilite_offset;
11159
11160 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor(_T ( "YELO1" )), 172);
11161
11162 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" ))));
11163 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
11164
11165 dc.DrawText(first, xp, yp);
11166 if (second.Len()) dc.DrawText(second, xp, yp + h1);
11167}
11168
11169void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11170 if (!g_bAllowShipToActive) return;
11171
11172 Route *rt = g_pRouteMan->GetpActiveRoute();
11173 if (!rt) return;
11174
11175 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11176 wxPoint2DDouble pa, pb;
11177 GetDoubleCanvasPointPix(gLat, gLon, &pa);
11178 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11179
11180 // set pen
11181 int width =
11182 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11183 if (rt->m_width != wxPENSTYLE_INVALID)
11184 width = rt->m_width; // set route pen style if any
11185 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11186 g_shipToActiveStyle, 5)]; // get setting pen style
11187 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11188 wxColour color =
11189 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11190 : // set setting route pen color
11191 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11192 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11193
11194 dc.SetPen(*mypen);
11195 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11196
11197 if (!Use_Opengl)
11198 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11199 (int)pb.m_y, GetVP(), true);
11200
11201#ifdef ocpnUSE_GL
11202 else {
11203#ifdef USE_ANDROID_GLES2
11204 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11205#else
11206 if (style != wxPENSTYLE_SOLID) {
11207 if (glChartCanvas::dash_map.find(style) !=
11208 glChartCanvas::dash_map.end()) {
11209 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11210 dc.SetPen(*mypen);
11211 }
11212 }
11213 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11214#endif
11215
11216 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11217 (int)pb.m_x, (int)pb.m_y, GetVP());
11218 }
11219#endif
11220 }
11221}
11222
11223void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11224 Route *route = 0;
11225 if (m_routeState >= 2) route = m_pMouseRoute;
11226 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11227 route = m_pMeasureRoute;
11228
11229 if (!route) return;
11230
11231 // Validate route pointer
11232 if (!g_pRouteMan->IsRouteValid(route)) return;
11233
11234 double render_lat = m_cursor_lat;
11235 double render_lon = m_cursor_lon;
11236
11237 int np = route->GetnPoints();
11238 if (np) {
11239 if (g_btouch && (np > 1)) np--;
11240 RoutePoint rp = route->GetPoint(np);
11241 render_lat = rp.m_lat;
11242 render_lon = rp.m_lon;
11243 }
11244
11245 double rhumbBearing, rhumbDist;
11246 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11247 &rhumbBearing, &rhumbDist);
11248 double brg = rhumbBearing;
11249 double dist = rhumbDist;
11250
11251 // Skip GreatCircle rubberbanding on touch devices.
11252 if (!g_btouch) {
11253 double gcBearing, gcBearing2, gcDist;
11254 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11255 m_cursor_lat, &gcDist, &gcBearing,
11256 &gcBearing2);
11257 double gcDistm = gcDist / 1852.0;
11258
11259 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11260 rhumbBearing = 90.;
11261
11262 wxPoint destPoint, lastPoint;
11263
11264 route->m_NextLegGreatCircle = false;
11265 int milesDiff = rhumbDist - gcDistm;
11266 if (milesDiff > 1) {
11267 brg = gcBearing;
11268 dist = gcDistm;
11269 route->m_NextLegGreatCircle = true;
11270 }
11271
11272 // FIXME (MacOS, the first segment is rendered wrong)
11273 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11274 &lastPoint);
11275
11276 if (route->m_NextLegGreatCircle) {
11277 for (int i = 1; i <= milesDiff; i++) {
11278 double p = (double)i * (1.0 / (double)milesDiff);
11279 double pLat, pLon;
11280 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11281 &pLon, &pLat, &gcBearing2);
11282 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11283 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11284 false);
11285 lastPoint = destPoint;
11286 }
11287 } else {
11288 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11289 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11290 false);
11291 if (m_bMeasure_DistCircle) {
11292 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11293 powf((float)(r_rband.y - lastPoint.y), 2));
11294
11295 dc.SetPen(*g_pRouteMan->GetRoutePen());
11296 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11297 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11298 }
11299 }
11300 }
11301 }
11302
11303 wxString routeInfo;
11304 double varBrg = 0;
11305 if (g_bShowTrue)
11306 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11307 0x00B0);
11308
11309 if (g_bShowMag) {
11310 double latAverage = (m_cursor_lat + render_lat) / 2;
11311 double lonAverage = (m_cursor_lon + render_lon) / 2;
11312 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11313
11314 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11315 (int)varBrg, 0x00B0);
11316 }
11317 routeInfo << _T(" ") << FormatDistanceAdaptive(dist);
11318
11319 // To make it easier to use a route as a bearing on a charted object add for
11320 // the first leg also the reverse bearing.
11321 if (np == 1) {
11322 routeInfo << "\nReverse: ";
11323 if (g_bShowTrue)
11324 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11325 (int)(brg + 180.) % 360, 0x00B0);
11326 if (g_bShowMag)
11327 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11328 (int)(varBrg + 180.) % 360, 0x00B0);
11329 }
11330
11331 wxString s0;
11332 if (!route->m_bIsInLayer)
11333 s0.Append(_("Route") + _T(": "));
11334 else
11335 s0.Append(_("Layer Route: "));
11336
11337 double disp_length = route->m_route_length;
11338 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11339 s0 += FormatDistanceAdaptive(disp_length);
11340
11341 RouteLegInfo(dc, r_rband, routeInfo, s0);
11342
11343 m_brepaint_piano = true;
11344}
11345
11346void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11347 if (!m_bShowVisibleSectors) return;
11348
11349 if (g_bDeferredInitDone) {
11350 // need to re-evaluate sectors?
11351 double rhumbBearing, rhumbDist;
11352 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11353 &rhumbBearing, &rhumbDist);
11354
11355 if (rhumbDist > 0.05) // miles
11356 {
11357 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11358 m_sectorlegsVisible);
11359 m_sector_glat = gLat;
11360 m_sector_glon = gLon;
11361 }
11362 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11363 }
11364}
11365
11366void ChartCanvas::WarpPointerDeferred(int x, int y) {
11367 warp_x = x;
11368 warp_y = y;
11369 warp_flag = true;
11370}
11371
11372int s_msg;
11373
11374void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11375 if (!ps52plib) return;
11376
11377 if (VPoint.b_quilt) { // quilted
11378 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11379
11380 if (m_pQuilt->IsQuiltVector()) {
11381 if (ps52plib->GetStateHash() != m_s52StateHash) {
11382 UpdateS52State();
11383 m_s52StateHash = ps52plib->GetStateHash();
11384 }
11385 }
11386 } else {
11387 if (ps52plib->GetStateHash() != m_s52StateHash) {
11388 UpdateS52State();
11389 m_s52StateHash = ps52plib->GetStateHash();
11390 }
11391 }
11392
11393 // Plugin charts
11394 bool bSendPlibState = true;
11395 if (VPoint.b_quilt) { // quilted
11396 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11397 }
11398
11399 if (bSendPlibState) {
11400 wxJSONValue v;
11401 v[_T("OpenCPN Version Major")] = VERSION_MAJOR;
11402 v[_T("OpenCPN Version Minor")] = VERSION_MINOR;
11403 v[_T("OpenCPN Version Patch")] = VERSION_PATCH;
11404 v[_T("OpenCPN Version Date")] = VERSION_DATE;
11405 v[_T("OpenCPN Version Full")] = VERSION_FULL;
11406
11407 // S52PLIB state
11408 v[_T("OpenCPN S52PLIB ShowText")] = GetShowENCText();
11409 v[_T("OpenCPN S52PLIB ShowSoundings")] = GetShowENCDepth();
11410 v[_T("OpenCPN S52PLIB ShowLights")] = GetShowENCLights();
11411 v[_T("OpenCPN S52PLIB ShowAnchorConditions")] = m_encShowAnchor;
11412 v[_T("OpenCPN S52PLIB ShowQualityOfData")] = GetShowENCDataQual();
11413 v[_T("OpenCPN S52PLIB ShowATONLabel")] = GetShowENCBuoyLabels();
11414 v[_T("OpenCPN S52PLIB ShowLightDescription")] = GetShowENCLightDesc();
11415
11416 v[_T("OpenCPN S52PLIB DisplayCategory")] = GetENCDisplayCategory();
11417
11418 v[_T("OpenCPN S52PLIB SoundingsFactor")] = g_ENCSoundingScaleFactor;
11419 v[_T("OpenCPN S52PLIB TextFactor")] = g_ENCTextScaleFactor;
11420
11421 // Global S52 options
11422
11423 v[_T("OpenCPN S52PLIB MetaDisplay")] = ps52plib->m_bShowMeta;
11424 v[_T("OpenCPN S52PLIB DeclutterText")] = ps52plib->m_bDeClutterText;
11425 v[_T("OpenCPN S52PLIB ShowNationalText")] = ps52plib->m_bShowNationalTexts;
11426 v[_T("OpenCPN S52PLIB ShowImportantTextOnly")] =
11427 ps52plib->m_bShowS57ImportantTextOnly;
11428 v[_T("OpenCPN S52PLIB UseSCAMIN")] = ps52plib->m_bUseSCAMIN;
11429 v[_T("OpenCPN S52PLIB UseSUPER_SCAMIN")] = ps52plib->m_bUseSUPER_SCAMIN;
11430 v[_T("OpenCPN S52PLIB SymbolStyle")] = ps52plib->m_nSymbolStyle;
11431 v[_T("OpenCPN S52PLIB BoundaryStyle")] = ps52plib->m_nBoundaryStyle;
11432 v[_T("OpenCPN S52PLIB ColorShades")] =
11433 S52_getMarinerParam(S52_MAR_TWO_SHADES);
11434
11435 // Some global GUI parameters, for completeness
11436 v[_T("OpenCPN Zoom Mod Vector")] = g_chart_zoom_modifier_vector;
11437 v[_T("OpenCPN Zoom Mod Other")] = g_chart_zoom_modifier_raster;
11438 v[_T("OpenCPN Scale Factor Exp")] =
11439 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11440 v[_T("OpenCPN Display Width")] = (int)g_display_size_mm;
11441
11442 wxJSONWriter w;
11443 wxString out;
11444 w.Write(v, out);
11445
11446 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11447 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11448 g_lastS52PLIBPluginMessage = out;
11449 }
11450 }
11451}
11452int spaint;
11453int s_in_update;
11454void ChartCanvas::OnPaint(wxPaintEvent &event) {
11455 wxPaintDC dc(this);
11456
11457 // GetToolbar()->Show( m_bToolbarEnable );
11458
11459 // Paint updates may have been externally disabled (temporarily, to avoid
11460 // Yield() recursion performance loss) It is important that the wxPaintDC is
11461 // built, even if we elect to not process this paint message. Otherwise, the
11462 // paint message may not be removed from the message queue, esp on Windows.
11463 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11464
11465 if (!m_b_paint_enable) {
11466 return;
11467 }
11468
11469 // If necessary, reconfigure the S52 PLIB
11470 UpdateCanvasS52PLIBConfig();
11471
11472#ifdef ocpnUSE_GL
11473 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11474
11475 if (m_glcc && g_bopengl) {
11476 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11477 s_in_update++;
11478 m_glcc->Update();
11479 s_in_update--;
11480 }
11481
11482 return;
11483 }
11484#endif
11485
11486 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11487
11488 wxRegion ru = GetUpdateRegion();
11489
11490 int rx, ry, rwidth, rheight;
11491 ru.GetBox(rx, ry, rwidth, rheight);
11492 // printf("%d Onpaint update region box: %d %d %d %d\n", spaint++, rx, ry,
11493 // rwidth, rheight);
11494
11495#ifdef ocpnUSE_DIBSECTION
11496 ocpnMemDC temp_dc;
11497#else
11498 wxMemoryDC temp_dc;
11499#endif
11500
11501 long height = GetVP().pix_height;
11502
11503#ifdef __WXMAC__
11504 // On OS X we have to explicitly extend the region for the piano area
11505 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11506 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11507 height += m_Piano->GetHeight();
11508#endif // __WXMAC__
11509 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11510
11511 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11512 if (pthumbwin) {
11513 int thumbx, thumby, thumbsx, thumbsy;
11514 pthumbwin->GetPosition(&thumbx, &thumby);
11515 pthumbwin->GetSize(&thumbsx, &thumbsy);
11516 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11517
11518 if (pthumbwin->IsShown()) {
11519 rgn_chart.Subtract(rgn_thumbwin);
11520 ru.Subtract(rgn_thumbwin);
11521 }
11522 }
11523
11524 // subtract the chart bar if it isn't transparent, and determine if we need to
11525 // paint it
11526 wxRegion rgn_blit = ru;
11527 if (g_bShowChartBar) {
11528 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11529 GetClientSize().x, m_Piano->GetHeight());
11530
11531 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11532 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11533 if (style->chartStatusWindowTransparent)
11534 m_brepaint_piano = true;
11535 else
11536 ru.Subtract(chart_bar_rect);
11537 }
11538 }
11539
11540 if (m_Compass && m_Compass->IsShown()) {
11541 wxRect compassRect = m_Compass->GetRect();
11542 if (ru.Contains(compassRect) != wxOutRegion) {
11543 ru.Subtract(compassRect);
11544 }
11545 }
11546
11547 wxRect noteRect = m_notification_button->GetRect();
11548 if (ru.Contains(noteRect) != wxOutRegion) {
11549 ru.Subtract(noteRect);
11550 }
11551
11552 // Is this viewpoint the same as the previously painted one?
11553 bool b_newview = true;
11554
11555 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11556 (m_cache_vp.rotation == VPoint.rotation) &&
11557 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11558 m_cache_vp.IsValid()) {
11559 b_newview = false;
11560 }
11561
11562 // If the ViewPort is skewed or rotated, we may be able to use the cached
11563 // rotated bitmap.
11564 bool b_rcache_ok = false;
11565 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11566 b_rcache_ok = !b_newview;
11567
11568 // Make a special VP
11569 if (VPoint.b_MercatorProjectionOverride)
11570 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11571 ViewPort svp = VPoint;
11572
11573 svp.pix_width = svp.rv_rect.width;
11574 svp.pix_height = svp.rv_rect.height;
11575
11576 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11577 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11578 // VPoint.rv_rect.height);
11579
11580 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11581
11582 // If we are going to use the cached rotated image, there is no need to fetch
11583 // any chart data and this will do it...
11584 if (b_rcache_ok) chart_get_region.Clear();
11585
11586 // Blit pan acceleration
11587 if (VPoint.b_quilt) // quilted
11588 {
11589 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11590
11591 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11592
11593 bool busy = false;
11594 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11595 m_cache_vp.rotation != VPoint.rotation)) {
11596 AbstractPlatform::ShowBusySpinner();
11597 busy = true;
11598 }
11599
11600 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11601 (m_working_bm.GetHeight() != svp.pix_height))
11602 m_working_bm.Create(svp.pix_width, svp.pix_height,
11603 -1); // make sure the target is big enoug
11604
11605 if (fabs(VPoint.rotation) < 0.01) {
11606 bool b_save = true;
11607
11608 if (g_SencThreadManager) {
11609 if (g_SencThreadManager->GetJobCount()) {
11610 b_save = false;
11611 m_cache_vp.Invalidate();
11612 }
11613 }
11614
11615 // If the saved wxBitmap from last OnPaint is useable
11616 // calculate the blit parameters
11617
11618 // We can only do screen blit painting if subsequent ViewPorts differ by
11619 // whole pixels So, in small scale bFollow mode, force the full screen
11620 // render. This seems a hack....There may be better logic here.....
11621
11622 // if(m_bFollow)
11623 // b_save = false;
11624
11625 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11626 if (b_newview) {
11627 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11628 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11629
11630 int dy = c_new.y - c_old.y;
11631 int dx = c_new.x - c_old.x;
11632
11633 // printf("In OnPaint Trying Blit dx: %d
11634 // dy:%d\n\n", dx, dy);
11635
11636 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11637 if (dx || dy) {
11638 // Blit the reuseable portion of the cached wxBitmap to a working
11639 // bitmap
11640 temp_dc.SelectObject(m_working_bm);
11641
11642 wxMemoryDC cache_dc;
11643 cache_dc.SelectObject(m_cached_chart_bm);
11644
11645 if (dy > 0) {
11646 if (dx > 0) {
11647 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11648 VPoint.pix_height - dy, &cache_dc, dx, dy);
11649 } else {
11650 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11651 VPoint.pix_height - dy, &cache_dc, 0, dy);
11652 }
11653
11654 } else {
11655 if (dx > 0) {
11656 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11657 VPoint.pix_height + dy, &cache_dc, dx, 0);
11658 } else {
11659 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11660 VPoint.pix_height + dy, &cache_dc, 0, 0);
11661 }
11662 }
11663
11664 OCPNRegion update_region;
11665 if (dy) {
11666 if (dy > 0)
11667 update_region.Union(
11668 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11669 else
11670 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11671 }
11672
11673 if (dx) {
11674 if (dx > 0)
11675 update_region.Union(
11676 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11677 else
11678 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11679 }
11680
11681 // Render the new region
11682 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11683 update_region);
11684 cache_dc.SelectObject(wxNullBitmap);
11685 } else {
11686 // No sensible (dx, dy) change in the view, so use the cached
11687 // member bitmap
11688 temp_dc.SelectObject(m_cached_chart_bm);
11689 b_save = false;
11690 }
11691 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11692
11693 } else // not blitable
11694 {
11695 temp_dc.SelectObject(m_working_bm);
11696 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11697 chart_get_region);
11698 }
11699 } else {
11700 // No change in the view, so use the cached member bitmap2
11701 temp_dc.SelectObject(m_cached_chart_bm);
11702 b_save = false;
11703 }
11704 } else // cached bitmap is not yet valid
11705 {
11706 temp_dc.SelectObject(m_working_bm);
11707 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11708 chart_get_region);
11709 }
11710
11711 // Save the fully rendered quilt image as a wxBitmap member of this class
11712 if (b_save) {
11713 // if((m_cached_chart_bm.GetWidth() !=
11714 // svp.pix_width) ||
11715 // (m_cached_chart_bm.GetHeight() !=
11716 // svp.pix_height))
11717 // m_cached_chart_bm.Create(svp.pix_width,
11718 // svp.pix_height, -1); // target wxBitmap
11719 // is big enough
11720 wxMemoryDC scratch_dc_0;
11721 scratch_dc_0.SelectObject(m_cached_chart_bm);
11722 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11723
11724 scratch_dc_0.SelectObject(wxNullBitmap);
11725
11726 m_bm_cache_vp =
11727 VPoint; // save the ViewPort associated with the cached wxBitmap
11728 }
11729 }
11730
11731 else // quilted, rotated
11732 {
11733 temp_dc.SelectObject(m_working_bm);
11734 OCPNRegion chart_get_all_region(
11735 wxRect(0, 0, svp.pix_width, svp.pix_height));
11736 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11737 chart_get_all_region);
11738 }
11739
11740 AbstractPlatform::HideBusySpinner();
11741
11742 }
11743
11744 else // not quilted
11745 {
11746 if (!m_singleChart) {
11747 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
11748 dc.Clear();
11749 return;
11750 }
11751
11752 if (!chart_get_region.IsEmpty()) {
11753 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
11754 }
11755 }
11756
11757 if (temp_dc.IsOk()) {
11758 // Arrange to render the World Chart vector data behind the rendered
11759 // current chart so that uncovered canvas areas show at least the world
11760 // chart.
11761 OCPNRegion chartValidRegion;
11762 if (!VPoint.b_quilt) {
11763 // Make a region covering the current chart on the canvas
11764
11765 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
11766 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
11767 else {
11768 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
11769 // require that the viewport passed here have pix_width and pix_height
11770 // set to the actual display, not the virtual (rv_rect) sizes
11771 // (the vector calculations require the virtual sizes in svp)
11772
11773 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
11774 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
11775 }
11776 } else
11777 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
11778
11779 temp_dc.DestroyClippingRegion();
11780
11781 // Copy current chart region
11782 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
11783
11784 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
11785
11786 if (!backgroundRegion.IsEmpty()) {
11787 // Draw the Background Chart only in the areas NOT covered by the
11788 // current chart view
11789
11790 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
11791 clipping regions with more than 1 rectangle so... */
11792 wxColour water = pWorldBackgroundChart->water;
11793 if (water.IsOk()) {
11794 temp_dc.SetPen(*wxTRANSPARENT_PEN);
11795 temp_dc.SetBrush(wxBrush(water));
11796 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
11797 while (upd.HaveRects()) {
11798 wxRect rect = upd.GetRect();
11799 temp_dc.DrawRectangle(rect);
11800 upd.NextRect();
11801 }
11802 }
11803 // Associate with temp_dc
11804 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
11805 temp_dc.SetDeviceClippingRegion(*clip_region);
11806 delete clip_region;
11807
11808 ocpnDC bgdc(temp_dc);
11809 double r = VPoint.rotation;
11810 SetVPRotation(VPoint.skew);
11811
11812 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
11813 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
11814
11815 SetVPRotation(r);
11816 }
11817 } // temp_dc.IsOk();
11818
11819 wxMemoryDC *pChartDC = &temp_dc;
11820 wxMemoryDC rotd_dc;
11821
11822 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
11823 // Can we use the current rotated image cache?
11824 if (!b_rcache_ok) {
11825#ifdef __WXMSW__
11826 wxMemoryDC tbase_dc;
11827 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
11828 tbase_dc.SelectObject(bm_base);
11829 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11830 tbase_dc.SelectObject(wxNullBitmap);
11831#else
11832 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
11833#endif
11834
11835 wxImage base_image;
11836 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
11837
11838 // Use a local static image rotator to improve wxWidgets code profile
11839 // Especially, on GTK the wxRound and wxRealPoint functions are very
11840 // expensive.....
11841
11842 double angle = GetVP().skew - GetVP().rotation;
11843 wxImage ri;
11844 bool b_rot_ok = false;
11845 if (base_image.IsOk()) {
11846 ViewPort rot_vp = GetVP();
11847
11848 m_b_rot_hidef = false;
11849
11850 ri = Image_Rotate(
11851 base_image, angle,
11852 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
11853 m_b_rot_hidef, &m_roffset);
11854
11855 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11856 (rot_vp.rotation == VPoint.rotation) &&
11857 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
11858 rot_vp.IsValid() && (ri.IsOk())) {
11859 b_rot_ok = true;
11860 }
11861 }
11862
11863 if (b_rot_ok) {
11864 delete m_prot_bm;
11865 m_prot_bm = new wxBitmap(ri);
11866 }
11867
11868 m_roffset.x += VPoint.rv_rect.x;
11869 m_roffset.y += VPoint.rv_rect.y;
11870 }
11871
11872 if (m_prot_bm && m_prot_bm->IsOk()) {
11873 rotd_dc.SelectObject(*m_prot_bm);
11874 pChartDC = &rotd_dc;
11875 } else {
11876 pChartDC = &temp_dc;
11877 m_roffset = wxPoint(0, 0);
11878 }
11879 } else { // unrotated
11880 pChartDC = &temp_dc;
11881 m_roffset = wxPoint(0, 0);
11882 }
11883
11884 wxPoint offset = m_roffset;
11885
11886 // Save the PixelCache viewpoint for next time
11887 m_cache_vp = VPoint;
11888
11889 // Set up a scratch DC for overlay objects
11890 wxMemoryDC mscratch_dc;
11891 mscratch_dc.SelectObject(*pscratch_bm);
11892
11893 mscratch_dc.ResetBoundingBox();
11894 mscratch_dc.DestroyClippingRegion();
11895 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
11896
11897 // Blit the externally invalidated areas of the chart onto the scratch dc
11898 wxRegionIterator upd(rgn_blit); // get the update rect list
11899 while (upd) {
11900 wxRect rect = upd.GetRect();
11901
11902 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
11903 rect.x - offset.x, rect.y - offset.y);
11904 upd++;
11905 }
11906
11907 // If multi-canvas, indicate which canvas has keyboard focus
11908 // by drawing a simple blue bar at the top.
11909 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
11910 if (this == wxWindow::FindFocus()) {
11911 g_focusCanvas = this;
11912
11913 wxColour colour = GetGlobalColor(_T("BLUE4"));
11914 mscratch_dc.SetPen(wxPen(colour));
11915 mscratch_dc.SetBrush(wxBrush(colour));
11916
11917 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
11918 mscratch_dc.DrawRectangle(activeRect);
11919 }
11920 }
11921
11922 // Any MBtiles?
11923 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
11924 unsigned int im = stackIndexArray.size();
11925 if (VPoint.b_quilt && im > 0) {
11926 std::vector<int> tiles_to_show;
11927 for (unsigned int is = 0; is < im; is++) {
11928 const ChartTableEntry &cte =
11929 ChartData->GetChartTableEntry(stackIndexArray[is]);
11930 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
11931 continue;
11932 }
11933 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
11934 tiles_to_show.push_back(stackIndexArray[is]);
11935 }
11936 }
11937
11938 if (tiles_to_show.size())
11939 SetAlertString(_("MBTile requires OpenGL to be enabled"));
11940 }
11941
11942 // May get an unexpected OnPaint call while switching display modes
11943 // Guard for that.
11944 if (!g_bopengl) {
11945 ocpnDC scratch_dc(mscratch_dc);
11946 RenderAlertMessage(mscratch_dc, GetVP());
11947 }
11948
11949#if 0
11950 // quiting?
11951 if (g_bquiting) {
11952#ifdef ocpnUSE_DIBSECTION
11953 ocpnMemDC q_dc;
11954#else
11955 wxMemoryDC q_dc;
11956#endif
11957 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
11958 q_dc.SelectObject(qbm);
11959
11960 // Get a copy of the screen
11961 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
11962
11963 // Draw a rectangle over the screen with a stipple brush
11964 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
11965 q_dc.SetBrush(qbr);
11966 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
11967
11968 // Blit back into source
11969 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
11970 wxCOPY);
11971
11972 q_dc.SelectObject(wxNullBitmap);
11973 }
11974#endif
11975
11976#if 0
11977 // It is possible that this two-step method may be reuired for some platforms.
11978 // So, retain in the code base to aid recovery if necessary
11979
11980 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
11981 if( VPoint.b_quilt ) {
11982 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
11983 ChartBase *chart = m_pQuilt->GetRefChart();
11984 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
11985
11986 // Clear the text Global declutter list
11987 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
11988 if(ChPI)
11989 ChPI->ClearPLIBTextList();
11990 else{
11991 if(ps52plib)
11992 ps52plib->ClearTextList();
11993 }
11994
11995 wxMemoryDC t_dc;
11996 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
11997
11998 wxColor maskBackground = wxColour(1,0,0);
11999 t_dc.SelectObject( qbm );
12000 t_dc.SetBackground(wxBrush(maskBackground));
12001 t_dc.Clear();
12002
12003 // Copy the scratch DC into the new bitmap
12004 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12005
12006 // Render the text to the new bitmap
12007 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12008 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12009
12010 // Copy the new bitmap back to the scratch dc
12011 wxRegionIterator upd_final( ru );
12012 while( upd_final ) {
12013 wxRect rect = upd_final.GetRect();
12014 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12015 upd_final++;
12016 }
12017
12018 t_dc.SelectObject( wxNullBitmap );
12019 }
12020 }
12021 }
12022#endif
12023 // Direct rendering model...
12024 if (VPoint.b_quilt) {
12025 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12026 ChartBase *chart = m_pQuilt->GetRefChart();
12027 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12028 // Clear the text Global declutter list
12029 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12030 if (ChPI)
12031 ChPI->ClearPLIBTextList();
12032 else {
12033 if (ps52plib) ps52plib->ClearTextList();
12034 }
12035
12036 // Render the text directly to the scratch bitmap
12037 OCPNRegion chart_all_text_region(
12038 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12039
12040 if (g_bShowChartBar && m_Piano) {
12041 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12042 GetVP().pix_width, m_Piano->GetHeight());
12043
12044 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12045 if (!style->chartStatusWindowTransparent)
12046 chart_all_text_region.Subtract(chart_bar_rect);
12047 }
12048
12049 if (m_Compass && m_Compass->IsShown()) {
12050 wxRect compassRect = m_Compass->GetRect();
12051 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12052 chart_all_text_region.Subtract(compassRect);
12053 }
12054 }
12055
12056 mscratch_dc.DestroyClippingRegion();
12057
12058 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12059 chart_all_text_region);
12060 }
12061 }
12062 }
12063
12064 // Now that charts are fully rendered, apply the overlay objects as decals.
12065 ocpnDC scratch_dc(mscratch_dc);
12066 DrawOverlayObjects(scratch_dc, ru);
12067
12068 // And finally, blit the scratch dc onto the physical dc
12069 wxRegionIterator upd_final(rgn_blit);
12070 while (upd_final) {
12071 wxRect rect = upd_final.GetRect();
12072 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12073 rect.y);
12074 upd_final++;
12075 }
12076
12077 // Deselect the chart bitmap from the temp_dc, so that it will not be
12078 // destroyed in the temp_dc dtor
12079 temp_dc.SelectObject(wxNullBitmap);
12080 // And for the scratch bitmap
12081 mscratch_dc.SelectObject(wxNullBitmap);
12082
12083 dc.DestroyClippingRegion();
12084
12085 PaintCleanup();
12086}
12087
12088void ChartCanvas::PaintCleanup() {
12089 // Handle the current graphic window, if present
12090
12091 if (pCwin) {
12092 pCwin->Show();
12093 if (m_bTCupdate) {
12094 pCwin->Refresh();
12095 pCwin->Update();
12096 }
12097 }
12098
12099 // And set flags for next time
12100 m_bTCupdate = false;
12101
12102 // Handle deferred WarpPointer
12103 if (warp_flag) {
12104 WarpPointer(warp_x, warp_y);
12105 warp_flag = false;
12106 }
12107
12108 // Start movement timers, this runs nearly immediately.
12109 // the reason we cannot simply call it directly is the
12110 // refresh events it emits may be blocked from this paint event
12111 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12112 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12113}
12114
12115#if 0
12116wxColour GetErrorGraphicColor(double val)
12117{
12118 /*
12119 double valm = wxMin(val_max, val);
12120
12121 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12122 unsigned char red = (unsigned char)(255 * (valm/val_max));
12123
12124 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12125
12126 hv.saturation = 1.0;
12127 hv.value = 1.0;
12128
12129 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12130 return wxColour(rv.red, rv.green, rv.blue);
12131 */
12132
12133 // HTML colors taken from NOAA WW3 Web representation
12134 wxColour c;
12135 if((val > 0) && (val < 1)) c.Set(_T("#002ad9"));
12136 else if((val >= 1) && (val < 2)) c.Set(_T("#006ed9"));
12137 else if((val >= 2) && (val < 3)) c.Set(_T("#00b2d9"));
12138 else if((val >= 3) && (val < 4)) c.Set(_T("#00d4d4"));
12139 else if((val >= 4) && (val < 5)) c.Set(_T("#00d9a6"));
12140 else if((val >= 5) && (val < 7)) c.Set(_T("#00d900"));
12141 else if((val >= 7) && (val < 9)) c.Set(_T("#95d900"));
12142 else if((val >= 9) && (val < 12)) c.Set(_T("#d9d900"));
12143 else if((val >= 12) && (val < 15)) c.Set(_T("#d9ae00"));
12144 else if((val >= 15) && (val < 18)) c.Set(_T("#d98300"));
12145 else if((val >= 18) && (val < 21)) c.Set(_T("#d95700"));
12146 else if((val >= 21) && (val < 24)) c.Set(_T("#d90000"));
12147 else if((val >= 24) && (val < 27)) c.Set(_T("#ae0000"));
12148 else if((val >= 27) && (val < 30)) c.Set(_T("#8c0000"));
12149 else if((val >= 30) && (val < 36)) c.Set(_T("#870000"));
12150 else if((val >= 36) && (val < 42)) c.Set(_T("#690000"));
12151 else if((val >= 42) && (val < 48)) c.Set(_T("#550000"));
12152 else if( val >= 48) c.Set(_T("#410000"));
12153
12154 return c;
12155}
12156
12157void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12158{
12159 wxImage gr_image(vp->pix_width, vp->pix_height);
12160 gr_image.InitAlpha();
12161
12162 double maxval = -10000;
12163 double minval = 10000;
12164
12165 double rlat, rlon;
12166 double glat, glon;
12167
12168 GetCanvasPixPoint(0, 0, rlat, rlon);
12169
12170 for(int i=1; i < vp->pix_height-1; i++)
12171 {
12172 for(int j=0; j < vp->pix_width; j++)
12173 {
12174 // Reference mercator value
12175// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12176
12177 // Georef value
12178 GetCanvasPixPoint(j, i, glat, glon);
12179
12180 maxval = wxMax(maxval, (glat - rlat));
12181 minval = wxMin(minval, (glat - rlat));
12182
12183 }
12184 rlat = glat;
12185 }
12186
12187 GetCanvasPixPoint(0, 0, rlat, rlon);
12188 for(int i=1; i < vp->pix_height-1; i++)
12189 {
12190 for(int j=0; j < vp->pix_width; j++)
12191 {
12192 // Reference mercator value
12193// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12194
12195 // Georef value
12196 GetCanvasPixPoint(j, i, glat, glon);
12197
12198 double f = ((glat - rlat)-minval)/(maxval - minval);
12199
12200 double dy = (f * 40);
12201
12202 wxColour c = GetErrorGraphicColor(dy);
12203 unsigned char r = c.Red();
12204 unsigned char g = c.Green();
12205 unsigned char b = c.Blue();
12206
12207 gr_image.SetRGB(j, i, r,g,b);
12208 if((glat - rlat )!= 0)
12209 gr_image.SetAlpha(j, i, 128);
12210 else
12211 gr_image.SetAlpha(j, i, 255);
12212
12213 }
12214 rlat = glat;
12215 }
12216
12217 // Create a Bitmap
12218 wxBitmap *pbm = new wxBitmap(gr_image);
12219 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12220 pbm->SetMask(gr_mask);
12221
12222 pmdc->DrawBitmap(*pbm, 0,0);
12223
12224 delete pbm;
12225
12226}
12227
12228#endif
12229
12230void ChartCanvas::CancelMouseRoute() {
12231 m_routeState = 0;
12232 m_pMouseRoute = NULL;
12233 m_bDrawingRoute = false;
12234}
12235
12236int ChartCanvas::GetNextContextMenuId() {
12237 return CanvasMenuHandler::GetNextContextMenuId();
12238}
12239
12240bool ChartCanvas::SetCursor(const wxCursor &c) {
12241#ifdef ocpnUSE_GL
12242 if (g_bopengl && m_glcc)
12243 return m_glcc->SetCursor(c);
12244 else
12245#endif
12246 return wxWindow::SetCursor(c);
12247}
12248
12249void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12250 if (g_bquiting) return;
12251 // Keep the mouse position members up to date
12252 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12253
12254 // Retrigger the route leg popup timer
12255 // This handles the case when the chart is moving in auto-follow mode,
12256 // but no user mouse input is made. The timer handler may Hide() the
12257 // popup if the chart moved enough n.b. We use slightly longer oneshot
12258 // value to allow this method's Refresh() to complete before potentially
12259 // getting another Refresh() in the popup timer handler.
12260 if (!m_RolloverPopupTimer.IsRunning() &&
12261 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12262 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12263 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12264 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12265
12266#ifdef ocpnUSE_GL
12267 if (m_glcc && g_bopengl) {
12268 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12269 // overlay objects.
12270 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12271
12272 m_glcc->Refresh(eraseBackground,
12273 NULL); // We always are going to render the entire screen
12274 // anyway, so make
12275 // sure that the window managers understand the invalid area
12276 // is actually the entire client area.
12277
12278 // We need to selectively Refresh some child windows, if they are visible.
12279 // Note that some children are refreshed elsewhere on timer ticks, so don't
12280 // need attention here.
12281
12282 // Thumbnail chart
12283 if (pthumbwin && pthumbwin->IsShown()) {
12284 pthumbwin->Raise();
12285 pthumbwin->Refresh(false);
12286 }
12287
12288 // ChartInfo window
12289 if (m_pCIWin && m_pCIWin->IsShown()) {
12290 m_pCIWin->Raise();
12291 m_pCIWin->Refresh(false);
12292 }
12293
12294 // if(g_MainToolbar)
12295 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12296
12297 } else
12298#endif
12299 wxWindow::Refresh(eraseBackground, rect);
12300}
12301
12302void ChartCanvas::Update() {
12303 if (m_glcc && g_bopengl) {
12304#ifdef ocpnUSE_GL
12305 m_glcc->Update();
12306#endif
12307 } else
12308 wxWindow::Update();
12309}
12310
12311void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12312 if (!pemboss) return;
12313 int x = pemboss->x, y = pemboss->y;
12314 const double factor = 200;
12315
12316 wxASSERT_MSG(dc.GetDC(), wxT("DrawEmboss has no dc (opengl?)"));
12317 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12318 wxASSERT_MSG(pmdc, wxT("dc to EmbossCanvas not a memory dc"));
12319
12320 // Grab a snipped image out of the chart
12321 wxMemoryDC snip_dc;
12322 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12323 snip_dc.SelectObject(snip_bmp);
12324
12325 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12326 snip_dc.SelectObject(wxNullBitmap);
12327
12328 wxImage snip_img = snip_bmp.ConvertToImage();
12329
12330 // Apply Emboss map to the snip image
12331 unsigned char *pdata = snip_img.GetData();
12332 if (pdata) {
12333 for (int y = 0; y < pemboss->height; y++) {
12334 int map_index = (y * pemboss->width);
12335 for (int x = 0; x < pemboss->width; x++) {
12336 double val = (pemboss->pmap[map_index] * factor) / 256.;
12337
12338 int nred = (int)((*pdata) + val);
12339 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12340 *pdata++ = (unsigned char)nred;
12341
12342 int ngreen = (int)((*pdata) + val);
12343 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12344 *pdata++ = (unsigned char)ngreen;
12345
12346 int nblue = (int)((*pdata) + val);
12347 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12348 *pdata++ = (unsigned char)nblue;
12349
12350 map_index++;
12351 }
12352 }
12353 }
12354
12355 // Convert embossed snip to a bitmap
12356 wxBitmap emb_bmp(snip_img);
12357
12358 // Map to another memoryDC
12359 wxMemoryDC result_dc;
12360 result_dc.SelectObject(emb_bmp);
12361
12362 // Blit to target
12363 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12364
12365 result_dc.SelectObject(wxNullBitmap);
12366}
12367
12368emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12369 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12370
12371 if (GetQuiltMode()) {
12372 // disable Overzoom indicator for MBTiles
12373 int refIndex = GetQuiltRefChartdbIndex();
12374 if (refIndex >= 0) {
12375 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12376 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12377 if (current_type == CHART_TYPE_MBTILES) {
12378 ChartBase *pChart = m_pQuilt->GetRefChart();
12379 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12380 if (ptc) {
12381 zoom_factor = ptc->GetZoomFactor();
12382 }
12383 }
12384 }
12385
12386 if (zoom_factor <= 3.9) return NULL;
12387 } else {
12388 if (m_singleChart) {
12389 if (zoom_factor <= 3.9) return NULL;
12390 } else
12391 return NULL;
12392 }
12393
12394 if (m_pEM_OverZoom) {
12395 m_pEM_OverZoom->x = 4;
12396 m_pEM_OverZoom->y = 0;
12397 if (g_MainToolbar && IsPrimaryCanvas()) {
12398 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12399 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12400 }
12401 }
12402 return m_pEM_OverZoom;
12403}
12404
12405void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12406 GridDraw(dc);
12407
12408 // bool pluginOverlayRender = true;
12409 //
12410 // if(g_canvasConfig > 0){ // Multi canvas
12411 // if(IsPrimaryCanvas())
12412 // pluginOverlayRender = false;
12413 // }
12414
12415 g_overlayCanvas = this;
12416
12417 if (g_pi_manager) {
12418 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12419 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12420 OVERLAY_LEGACY);
12421 }
12422
12423 AISDrawAreaNotices(dc, GetVP(), this);
12424
12425 wxDC *pdc = dc.GetDC();
12426 if (pdc) {
12427 pdc->DestroyClippingRegion();
12428 wxDCClipper(*pdc, ru);
12429 }
12430
12431 if (m_bShowNavobjects) {
12432 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12433 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12434 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12435 DrawAnchorWatchPoints(dc);
12436 } else {
12437 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12438 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12439 }
12440
12441 AISDraw(dc, GetVP(), this);
12442 ShipDraw(dc);
12443 AlertDraw(dc);
12444
12445 RenderVisibleSectorLights(dc);
12446
12447 RenderAllChartOutlines(dc, GetVP());
12448 RenderRouteLegs(dc);
12449 RenderShipToActive(dc, false);
12450 ScaleBarDraw(dc);
12451 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12452 if (g_pi_manager) {
12453 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12454 OVERLAY_OVER_SHIPS);
12455 }
12456
12457 DrawEmboss(dc, EmbossDepthScale());
12458 DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12459 if (g_pi_manager) {
12460 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12461 OVERLAY_OVER_EMBOSS);
12462 }
12463
12464 if (m_bShowTide) {
12465 RebuildTideSelectList(GetVP().GetBBox());
12466 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12467 }
12468
12469 if (m_bShowCurrent) {
12470 RebuildCurrentSelectList(GetVP().GetBBox());
12471 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12472 }
12473
12474 if (!g_PrintingInProgress) {
12475 if (IsPrimaryCanvas()) {
12476 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12477 }
12478
12479 if (IsPrimaryCanvas()) {
12480 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12481 }
12482
12483 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12484
12485 if (m_pTrackRolloverWin) {
12486 m_pTrackRolloverWin->Draw(dc);
12487 m_brepaint_piano = true;
12488 }
12489
12490 if (m_pRouteRolloverWin) {
12491 m_pRouteRolloverWin->Draw(dc);
12492 m_brepaint_piano = true;
12493 }
12494
12495 if (m_pAISRolloverWin) {
12496 m_pAISRolloverWin->Draw(dc);
12497 m_brepaint_piano = true;
12498 }
12499 if (m_brepaint_piano && g_bShowChartBar) {
12500 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12501 }
12502
12503 if (m_Compass) m_Compass->Paint(dc);
12504
12505 if (!g_CanvasHideNotificationIcon) {
12506 auto &noteman = NotificationManager::GetInstance();
12507 if (noteman.GetNotificationCount()) {
12508 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12509 if (m_notification_button->UpdateStatus()) Refresh();
12510 m_notification_button->Show(true);
12511 m_notification_button->Paint(dc);
12512 } else {
12513 m_notification_button->Show(false);
12514 }
12515 }
12516 }
12517 if (g_pi_manager) {
12518 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12519 OVERLAY_OVER_UI);
12520 }
12521}
12522
12523emboss_data *ChartCanvas::EmbossDepthScale() {
12524 if (!m_bShowDepthUnits) return NULL;
12525
12526 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12527
12528 if (GetQuiltMode()) {
12529 wxString s = m_pQuilt->GetQuiltDepthUnit();
12530 s.MakeUpper();
12531 if (s == _T("FEET"))
12532 depth_unit_type = DEPTH_UNIT_FEET;
12533 else if (s.StartsWith(_T("FATHOMS")))
12534 depth_unit_type = DEPTH_UNIT_FATHOMS;
12535 else if (s.StartsWith(_T("METERS")))
12536 depth_unit_type = DEPTH_UNIT_METERS;
12537 else if (s.StartsWith(_T("METRES")))
12538 depth_unit_type = DEPTH_UNIT_METERS;
12539 else if (s.StartsWith(_T("METRIC")))
12540 depth_unit_type = DEPTH_UNIT_METERS;
12541 else if (s.StartsWith(_T("METER")))
12542 depth_unit_type = DEPTH_UNIT_METERS;
12543
12544 } else {
12545 if (m_singleChart) {
12546 depth_unit_type = m_singleChart->GetDepthUnitType();
12547 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12548 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12549 }
12550 }
12551
12552 emboss_data *ped = NULL;
12553 switch (depth_unit_type) {
12554 case DEPTH_UNIT_FEET:
12555 ped = m_pEM_Feet;
12556 break;
12557 case DEPTH_UNIT_METERS:
12558 ped = m_pEM_Meters;
12559 break;
12560 case DEPTH_UNIT_FATHOMS:
12561 ped = m_pEM_Fathoms;
12562 break;
12563 default:
12564 return NULL;
12565 }
12566
12567 ped->x = (GetVP().pix_width - ped->width);
12568
12569 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12570 wxRect r = m_Compass->GetRect();
12571 ped->y = r.y + r.height;
12572 } else {
12573 ped->y = 40;
12574 }
12575 return ped;
12576}
12577
12578void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12579 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12580 wxFont font;
12581 if (style->embossFont == wxEmptyString) {
12582 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12583 font = *dFont;
12584 font.SetPointSize(60);
12585 font.SetWeight(wxFONTWEIGHT_BOLD);
12586 } else
12587 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12588 wxFONTWEIGHT_BOLD, false, style->embossFont);
12589
12590 int emboss_width = 500;
12591 int emboss_height = 200;
12592
12593 // Free any existing emboss maps
12594 delete m_pEM_Feet;
12595 delete m_pEM_Meters;
12596 delete m_pEM_Fathoms;
12597
12598 // Create the 3 DepthUnit emboss map structures
12599 m_pEM_Feet =
12600 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12601 m_pEM_Meters =
12602 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12603 m_pEM_Fathoms =
12604 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12605}
12606
12607#define OVERZOOM_TEXT _("OverZoom")
12608
12609void ChartCanvas::SetOverzoomFont() {
12610 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12611 int w, h;
12612
12613 wxFont font;
12614 if (style->embossFont == wxEmptyString) {
12615 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12616 font = *dFont;
12617 font.SetPointSize(40);
12618 font.SetWeight(wxFONTWEIGHT_BOLD);
12619 } else
12620 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12621 wxFONTWEIGHT_BOLD, false, style->embossFont);
12622
12623 wxClientDC dc(this);
12624 dc.SetFont(font);
12625 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12626
12627 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12628 font.SetPointSize(font.GetPointSize() - 1);
12629 dc.SetFont(font);
12630 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12631 }
12632 m_overzoomFont = font;
12633 m_overzoomTextWidth = w;
12634 m_overzoomTextHeight = h;
12635}
12636
12637void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12638 delete m_pEM_OverZoom;
12639
12640 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12641 m_pEM_OverZoom =
12642 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12643 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12644}
12645
12646emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12647 int height, const wxString &str,
12648 ColorScheme cs) {
12649 int *pmap;
12650
12651 // Create a temporary bitmap
12652 wxBitmap bmp(width, height, -1);
12653
12654 // Create a memory DC
12655 wxMemoryDC temp_dc;
12656 temp_dc.SelectObject(bmp);
12657
12658 // Paint on it
12659 temp_dc.SetBackground(*wxWHITE_BRUSH);
12660 temp_dc.SetTextBackground(*wxWHITE);
12661 temp_dc.SetTextForeground(*wxBLACK);
12662
12663 temp_dc.Clear();
12664
12665 temp_dc.SetFont(font);
12666
12667 int str_w, str_h;
12668 temp_dc.GetTextExtent(str, &str_w, &str_h);
12669 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12670 temp_dc.DrawText(str, 1, 1);
12671
12672 // Deselect the bitmap
12673 temp_dc.SelectObject(wxNullBitmap);
12674
12675 // Convert bitmap the wxImage for manipulation
12676 wxImage img = bmp.ConvertToImage();
12677
12678 int image_width = str_w * 105 / 100;
12679 int image_height = str_h * 105 / 100;
12680 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12681 wxMin(image_height, img.GetHeight()));
12682 wxImage imgs = img.GetSubImage(r);
12683
12684 double val_factor;
12685 switch (cs) {
12686 case GLOBAL_COLOR_SCHEME_DAY:
12687 default:
12688 val_factor = 1;
12689 break;
12690 case GLOBAL_COLOR_SCHEME_DUSK:
12691 val_factor = .5;
12692 break;
12693 case GLOBAL_COLOR_SCHEME_NIGHT:
12694 val_factor = .25;
12695 break;
12696 }
12697
12698 int val;
12699 int index;
12700 const int w = imgs.GetWidth();
12701 const int h = imgs.GetHeight();
12702 pmap = (int *)calloc(w * h * sizeof(int), 1);
12703 // Create emboss map by differentiating the emboss image
12704 // and storing integer results in pmap
12705 // n.b. since the image is B/W, it is sufficient to check
12706 // one channel (i.e. red) only
12707 for (int y = 1; y < h - 1; y++) {
12708 for (int x = 1; x < w - 1; x++) {
12709 val =
12710 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
12711 val = (int)(val * val_factor);
12712 index = (y * w) + x;
12713 pmap[index] = val;
12714 }
12715 }
12716
12717 emboss_data *pret = new emboss_data;
12718 pret->pmap = pmap;
12719 pret->width = w;
12720 pret->height = h;
12721
12722 return pret;
12723}
12724
12725void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12726 Track *active_track = NULL;
12727 for (Track *pTrackDraw : g_TrackList) {
12728 if (g_pActiveTrack == pTrackDraw) {
12729 active_track = pTrackDraw;
12730 continue;
12731 }
12732
12733 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
12734 }
12735
12736 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12737}
12738
12739void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12740 Track *active_track = NULL;
12741 for (Track *pTrackDraw : g_TrackList) {
12742 if (g_pActiveTrack == pTrackDraw) {
12743 active_track = pTrackDraw;
12744 break;
12745 }
12746 }
12747 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12748}
12749
12750void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12751 Route *active_route = NULL;
12752
12753 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12754 node = node->GetNext()) {
12755 Route *pRouteDraw = node->GetData();
12756 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12757 active_route = pRouteDraw;
12758 continue;
12759 }
12760
12761 // if(m_canvasIndex == 1)
12762 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
12763 }
12764
12765 // Draw any active or selected route (or track) last, so that is is always on
12766 // top
12767 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12768}
12769
12770void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12771 Route *active_route = NULL;
12772
12773 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12774 node = node->GetNext()) {
12775 Route *pRouteDraw = node->GetData();
12776 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12777 active_route = pRouteDraw;
12778 break;
12779 }
12780 }
12781 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12782}
12783
12784void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12785 if (!pWayPointMan) return;
12786
12787 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12788
12789 while (node) {
12790 RoutePoint *pWP = node->GetData();
12791 if (pWP) {
12792 if (pWP->m_bIsInRoute) {
12793 node = node->GetNext();
12794 continue;
12795 }
12796
12797 /* technically incorrect... waypoint has bounding box */
12798 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
12799 RoutePointGui(*pWP).Draw(dc, this, NULL);
12800 else {
12801 // Are Range Rings enabled?
12802 if (pWP->GetShowWaypointRangeRings() &&
12803 (pWP->GetWaypointRangeRingsNumber() > 0)) {
12804 double factor = 1.00;
12805 if (pWP->GetWaypointRangeRingsStepUnits() ==
12806 1) // convert kilometers to NMi
12807 factor = 1 / 1.852;
12808
12809 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
12810 pWP->GetWaypointRangeRingsStep() / 60.;
12811 radius *= 2; // Fudge factor
12812
12813 LLBBox radar_box;
12814 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
12815 pWP->m_lat + radius, pWP->m_lon + radius);
12816 if (!BltBBox.IntersectOut(radar_box)) {
12817 RoutePointGui(*pWP).Draw(dc, this, NULL);
12818 }
12819 }
12820 }
12821 }
12822
12823 node = node->GetNext();
12824 }
12825}
12826
12827void ChartCanvas::DrawBlinkObjects(void) {
12828 // All RoutePoints
12829 wxRect update_rect;
12830
12831 if (!pWayPointMan) return;
12832
12833 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12834
12835 while (node) {
12836 RoutePoint *pWP = node->GetData();
12837 if (pWP) {
12838 if (pWP->m_bBlink) {
12839 update_rect.Union(pWP->CurrentRect_in_DC);
12840 }
12841 }
12842
12843 node = node->GetNext();
12844 }
12845 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
12846}
12847
12848void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
12849 // draw anchor watch rings, if activated
12850
12851 if (pAnchorWatchPoint1 || pAnchorWatchPoint2) {
12852 wxPoint r1, r2;
12853 wxPoint lAnchorPoint1, lAnchorPoint2;
12854 double lpp1 = 0.0;
12855 double lpp2 = 0.0;
12856 if (pAnchorWatchPoint1) {
12857 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
12858 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
12859 &lAnchorPoint1);
12860 }
12861 if (pAnchorWatchPoint2) {
12862 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
12863 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
12864 &lAnchorPoint2);
12865 }
12866
12867 wxPen ppPeng(GetGlobalColor(_T ( "UGREN" )), 2);
12868 wxPen ppPenr(GetGlobalColor(_T ( "URED" )), 2);
12869
12870 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
12871 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
12872 dc.SetBrush(*ppBrush);
12873
12874 if (lpp1 > 0) {
12875 dc.SetPen(ppPeng);
12876 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12877 }
12878
12879 if (lpp2 > 0) {
12880 dc.SetPen(ppPeng);
12881 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12882 }
12883
12884 if (lpp1 < 0) {
12885 dc.SetPen(ppPenr);
12886 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12887 }
12888
12889 if (lpp2 < 0) {
12890 dc.SetPen(ppPenr);
12891 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12892 }
12893 }
12894}
12895
12896double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
12897 double lpp = 0.;
12898 wxPoint r1;
12899 wxPoint lAnchorPoint;
12900 double d1 = 0.0;
12901 double dabs;
12902 double tlat1, tlon1;
12903
12904 if (pAnchorWatchPoint) {
12905 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
12906 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
12907 dabs = fabs(d1 / 1852.);
12908 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
12909 &tlat1, &tlon1);
12910 GetCanvasPointPix(tlat1, tlon1, &r1);
12911 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
12912 &lAnchorPoint);
12913 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
12914 pow((double)(lAnchorPoint.y - r1.y), 2));
12915
12916 // This is an entry watch
12917 if (d1 < 0) lpp = -lpp;
12918 }
12919 return lpp;
12920}
12921
12922//------------------------------------------------------------------------------------------
12923// Tides Support
12924//------------------------------------------------------------------------------------------
12925void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
12926 if (!ptcmgr) return;
12927
12928 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
12929
12930 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12931 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12932 double lon = pIDX->IDX_lon;
12933 double lat = pIDX->IDX_lat;
12934
12935 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12936 if ((type == 't') || (type == 'T')) {
12937 if (BBox.Contains(lat, lon)) {
12938 // Manage the point selection list
12939 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
12940 }
12941 }
12942 }
12943}
12944
12945extern wxDateTime gTimeSource;
12946
12947void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
12948 if (!ptcmgr) return;
12949
12950 wxDateTime this_now = gTimeSource;
12951 bool cur_time = !gTimeSource.IsValid();
12952 if (cur_time) this_now = wxDateTime::Now();
12953 time_t t_this_now = this_now.GetTicks();
12954
12955 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
12956 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
12957 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
12958 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )), 1,
12959 wxPENSTYLE_SOLID);
12960 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
12961 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )), 1,
12962 wxPENSTYLE_SOLID);
12963
12964 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
12965 GetGlobalColor(_T ( "GREEN1" )), wxBRUSHSTYLE_SOLID);
12966 // wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush (
12967 // GetGlobalColor ( _T ( "UINFD" ) ), wxSOLID );
12968 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
12969 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )),
12970 wxBRUSHSTYLE_SOLID);
12971 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
12972 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )),
12973 wxBRUSHSTYLE_SOLID);
12974
12975 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
12976 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
12977 int font_size = wxMax(10, dFont->GetPointSize());
12978 font_size /= g_Platform->GetDisplayDIPMult(this);
12979 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
12980 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
12981 false, dFont->GetFaceName());
12982
12983 dc.SetPen(*pblack_pen);
12984 dc.SetBrush(*pgreen_brush);
12985
12986 wxBitmap bm;
12987 switch (m_cs) {
12988 case GLOBAL_COLOR_SCHEME_DAY:
12989 bm = m_bmTideDay;
12990 break;
12991 case GLOBAL_COLOR_SCHEME_DUSK:
12992 bm = m_bmTideDusk;
12993 break;
12994 case GLOBAL_COLOR_SCHEME_NIGHT:
12995 bm = m_bmTideNight;
12996 break;
12997 default:
12998 bm = m_bmTideDay;
12999 break;
13000 }
13001
13002 int bmw = bm.GetWidth();
13003 int bmh = bm.GetHeight();
13004
13005 float scale_factor = 1.0;
13006
13007 // Set the onscreen size of the symbol
13008 // Compensate for various display resolutions
13009 float icon_pixelRefDim = 45;
13010
13011#if 0
13012 float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *25 / 1000; // Intended physical rendered size onscreen
13013 nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 8);
13014 nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 15);
13015 float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
13016#endif
13017
13018#ifndef __ANDROID__
13019 // another method is simply to declare that the icon shall be x times the size
13020 // of a raster symbol (e.g.BOYLAT)
13021 // This is a bit of a hack that will suffice until until we get fully
13022 // scalable ENC symbol sets
13023 // float nominal_icon_size_pixels = 48; // 3 x 16
13024 // float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
13025
13026 // or, x times size of text font
13027 wxScreenDC sdc;
13028 int height;
13029 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13030 height *= g_Platform->GetDisplayDIPMult(this);
13031 float nominal_icon_size_pixels = 48; // 3 x 16
13032 float pix_factor = (2 * height) / nominal_icon_size_pixels;
13033
13034#else
13035 // Yet another method goes like this:
13036 // Set the onscreen size of the symbol
13037 // Compensate for various display resolutions
13038 // Develop empirically, making a symbol about 16 mm tall
13039 double symHeight =
13040 icon_pixelRefDim /
13041 GetPixPerMM(); // from draw instructions, symbol is xx pix high
13042 double targetHeight0 = 16.0;
13043
13044 // But we want to scale the size down for smaller displays
13045 double displaySize = m_display_size_mm;
13046 displaySize = wxMax(displaySize, 100);
13047
13048 float targetHeight = wxMin(targetHeight0, displaySize / 15);
13049
13050 double pix_factor = targetHeight / symHeight;
13051#endif
13052
13053 scale_factor *= pix_factor;
13054
13055 float user_scale_factor = g_ChartScaleFactorExp;
13056 if (g_ChartScaleFactorExp > 1.0)
13057 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13058 1.2; // soften the scale factor a bit
13059
13060 scale_factor *= user_scale_factor;
13061 scale_factor *= GetContentScaleFactor();
13062
13063 {
13064 double marge = 0.05;
13065 std::vector<LLBBox> drawn_boxes;
13066 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13067 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13068
13069 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13070 if ((type == 't') || (type == 'T')) // only Tides
13071 {
13072 double lon = pIDX->IDX_lon;
13073 double lat = pIDX->IDX_lat;
13074
13075 if (BBox.ContainsMarge(lat, lon, marge)) {
13076 // Avoid drawing detailed graphic for duplicate tide stations
13077 if (GetVP().chart_scale < 500000) {
13078 bool bdrawn = false;
13079 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13080 if (drawn_boxes[i].Contains(lat, lon)) {
13081 bdrawn = true;
13082 break;
13083 }
13084 }
13085 if (bdrawn) continue; // the station loop
13086
13087 LLBBox this_box;
13088 this_box.Set(lat, lon, lat, lon);
13089 this_box.EnLarge(.005);
13090 drawn_boxes.push_back(this_box);
13091 }
13092
13093 wxPoint r;
13094 GetCanvasPointPix(lat, lon, &r);
13095 // draw standard icons
13096 if (GetVP().chart_scale > 500000) {
13097 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13098 }
13099 // draw "extended" icons
13100 else {
13101 dc.SetFont(*plabelFont);
13102 {
13103 {
13104 float val, nowlev;
13105 float ltleve = 0.;
13106 float htleve = 0.;
13107 time_t tctime;
13108 time_t lttime = 0;
13109 time_t httime = 0;
13110 bool wt;
13111 // define if flood or ebb in the last ten minutes and verify if
13112 // data are useable
13113 if (ptcmgr->GetTideFlowSens(
13114 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13115 pIDX->IDX_rec_num, nowlev, val, wt)) {
13116 // search forward the first HW or LW near "now" ( starting at
13117 // "now" - ten minutes )
13118 ptcmgr->GetHightOrLowTide(
13119 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13120 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13121 wt, pIDX->IDX_rec_num, val, tctime);
13122 if (wt) {
13123 httime = tctime;
13124 htleve = val;
13125 } else {
13126 lttime = tctime;
13127 ltleve = val;
13128 }
13129 wt = !wt;
13130
13131 // then search opposite tide near "now"
13132 if (tctime > t_this_now) // search backward
13133 ptcmgr->GetHightOrLowTide(
13134 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13135 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13136 pIDX->IDX_rec_num, val, tctime);
13137 else
13138 // or search forward
13139 ptcmgr->GetHightOrLowTide(
13140 t_this_now, FORWARD_TEN_MINUTES_STEP,
13141 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13142 val, tctime);
13143 if (wt) {
13144 httime = tctime;
13145 htleve = val;
13146 } else {
13147 lttime = tctime;
13148 ltleve = val;
13149 }
13150
13151 // draw the tide rectangle:
13152
13153 // tide icon rectangle has default pre-scaled width = 12 ,
13154 // height = 45
13155 int width = (int)(12 * scale_factor + 0.5);
13156 int height = (int)(45 * scale_factor + 0.5);
13157 int linew = wxMax(1, (int)(scale_factor));
13158 int xDraw = r.x - (width / 2);
13159 int yDraw = r.y - (height / 2);
13160
13161 // process tide state ( %height and flow sens )
13162 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13163 int hs = (httime > lttime) ? -4 : 4;
13164 hs *= (int)(scale_factor + 0.5);
13165 if (ts > 0.995 || ts < 0.005) hs = 0;
13166 int ht_y = (int)(height * ts);
13167
13168 // draw yellow tide rectangle outlined in black
13169 pblack_pen->SetWidth(linew);
13170 dc.SetPen(*pblack_pen);
13171 dc.SetBrush(*pyelo_brush);
13172 dc.DrawRectangle(xDraw, yDraw, width, height);
13173
13174 // draw blue rectangle as water height, smaller in width than
13175 // yellow rectangle
13176 dc.SetPen(*pblue_pen);
13177 dc.SetBrush(*pblue_brush);
13178 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13179 (width - (4 * linew)), height - ht_y);
13180
13181 // draw sens arrows (ensure they are not "under-drawn" by top
13182 // line of blue rectangle )
13183 int hl;
13184 wxPoint arrow[3];
13185 arrow[0].x = xDraw + 2 * linew;
13186 arrow[1].x = xDraw + width / 2;
13187 arrow[2].x = xDraw + width - 2 * linew;
13188 pyelo_pen->SetWidth(linew);
13189 pblue_pen->SetWidth(linew);
13190 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13191 {
13192 hl = (int)(height * 0.25) + yDraw;
13193 arrow[0].y = hl;
13194 arrow[1].y = hl + hs;
13195 arrow[2].y = hl;
13196 if (ts < 0.15)
13197 dc.SetPen(*pyelo_pen);
13198 else
13199 dc.SetPen(*pblue_pen);
13200 dc.DrawLines(3, arrow);
13201 }
13202 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13203 {
13204 hl = (int)(height * 0.5) + yDraw;
13205 arrow[0].y = hl;
13206 arrow[1].y = hl + hs;
13207 arrow[2].y = hl;
13208 if (ts < 0.40)
13209 dc.SetPen(*pyelo_pen);
13210 else
13211 dc.SetPen(*pblue_pen);
13212 dc.DrawLines(3, arrow);
13213 }
13214 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13215 {
13216 hl = (int)(height * 0.75) + yDraw;
13217 arrow[0].y = hl;
13218 arrow[1].y = hl + hs;
13219 arrow[2].y = hl;
13220 if (ts < 0.65)
13221 dc.SetPen(*pyelo_pen);
13222 else
13223 dc.SetPen(*pblue_pen);
13224 dc.DrawLines(3, arrow);
13225 }
13226 // draw tide level text
13227 wxString s;
13228 s.Printf(_T("%3.1f"), nowlev);
13229 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13230 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13231 int wx1;
13232 dc.GetTextExtent(s, &wx1, NULL);
13233 wx1 *= g_Platform->GetDisplayDIPMult(this);
13234 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13235 }
13236 }
13237 }
13238 }
13239 }
13240 }
13241 }
13242 }
13243}
13244
13245//------------------------------------------------------------------------------------------
13246// Currents Support
13247//------------------------------------------------------------------------------------------
13248
13249void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13250 if (!ptcmgr) return;
13251
13252 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13253
13254 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13255 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13256 double lon = pIDX->IDX_lon;
13257 double lat = pIDX->IDX_lat;
13258
13259 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13260 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13261 if ((BBox.Contains(lat, lon))) {
13262 // Manage the point selection list
13263 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13264 }
13265 }
13266 }
13267}
13268
13269void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13270 if (!ptcmgr) return;
13271
13272 float tcvalue, dir;
13273 bool bnew_val;
13274 char sbuf[20];
13275 wxFont *pTCFont;
13276 double lon_last = 0.;
13277 double lat_last = 0.;
13278 // arrow size for Raz Blanchard : 12 knots north
13279 double marge = 0.2;
13280 bool cur_time = !gTimeSource.IsValid();
13281
13282 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13283 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13284
13285 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
13286 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
13287 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13288 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )), 1,
13289 wxPENSTYLE_SOLID);
13290 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13291 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )),
13292 wxBRUSHSTYLE_SOLID);
13293 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13294 GetGlobalColor(_T ( "UIBDR" )), wxBRUSHSTYLE_SOLID);
13295 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13296 GetGlobalColor(_T ( "UINFD" )), wxBRUSHSTYLE_SOLID);
13297
13298 double skew_angle = GetVPRotation();
13299
13300 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13301 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13302 int font_size = wxMax(10, dFont->GetPointSize());
13303 font_size /= g_Platform->GetDisplayDIPMult(this);
13304 pTCFont = FontMgr::Get().FindOrCreateFont(
13305 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13306 false, dFont->GetFaceName());
13307
13308 float scale_factor = 1.0;
13309
13310 // Set the onscreen size of the symbol
13311 // Compensate for various display resolutions
13312
13313#if 0
13314 float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *3 / 1000; // Intended physical rendered size onscreen
13315 nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 2);
13316 nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 4);
13317 float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
13318#endif
13319
13320#if 0
13321 // another method is simply to declare that the icon shall be x times the size of a raster symbol (e.g.BOYLAT)
13322 // This is a bit of a hack that will suffice until until we get fully scalable ENC symbol sets
13323 float nominal_icon_size_pixels = 6; // 16 / 3
13324 float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
13325#endif
13326
13327#ifndef __ANDROID__
13328 // or, x times size of text font
13329 wxScreenDC sdc;
13330 int height;
13331 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13332 height *= g_Platform->GetDisplayDIPMult(this);
13333 float nominal_icon_size_pixels = 15;
13334 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13335
13336#else
13337 // Yet another method goes like this:
13338 // Set the onscreen size of the symbol
13339 // Compensate for various display resolutions
13340 // Develop empirically....
13341 float icon_pixelRefDim = 5;
13342
13343 double symHeight =
13344 icon_pixelRefDim /
13345 GetPixPerMM(); // from draw instructions, symbol is xx pix high
13346 double targetHeight0 = 2.0;
13347
13348 // But we want to scale the size down for smaller displays
13349 double displaySize = m_display_size_mm;
13350 displaySize = wxMax(displaySize, 100);
13351
13352 float targetHeight = wxMin(targetHeight0, displaySize / 50);
13353 double pix_factor = targetHeight / symHeight;
13354#endif
13355
13356 scale_factor *= pix_factor;
13357
13358 float user_scale_factor = g_ChartScaleFactorExp;
13359 if (g_ChartScaleFactorExp > 1.0)
13360 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13361 1.2; // soften the scale factor a bit
13362
13363 scale_factor *= user_scale_factor;
13364
13365 scale_factor *= GetContentScaleFactor();
13366
13367 {
13368 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13369 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13370 double lon = pIDX->IDX_lon;
13371 double lat = pIDX->IDX_lat;
13372
13373 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13374 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13375 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13376 wxPoint r;
13377 GetCanvasPointPix(lat, lon, &r);
13378
13379 wxPoint d[4]; // points of a diamond at the current station location
13380 int dd = (int)(5.0 * scale_factor + 0.5);
13381 d[0].x = r.x;
13382 d[0].y = r.y + dd;
13383 d[1].x = r.x + dd;
13384 d[1].y = r.y;
13385 d[2].x = r.x;
13386 d[2].y = r.y - dd;
13387 d[3].x = r.x - dd;
13388 d[3].y = r.y;
13389
13390 if (1) {
13391 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13392 dc.SetPen(*pblack_pen);
13393 dc.SetBrush(*porange_brush);
13394 dc.DrawPolygon(4, d);
13395
13396 if (type == 'C') {
13397 dc.SetBrush(*pblack_brush);
13398 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13399 }
13400
13401 if (GetVP().chart_scale < 1000000) {
13402 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13403 continue;
13404 } else
13405 continue;
13406
13407 if (1 /*type == 'c'*/) {
13408 {
13409 // Get the display pixel location of the current station
13410 int pixxc, pixyc;
13411 pixxc = r.x;
13412 pixyc = r.y;
13413
13414 // Adjust drawing size using logarithmic scale. tcvalue is
13415 // current in knots
13416 double a1 = fabs(tcvalue) * 10.;
13417 // Current values <= 0.1 knot will have no arrow
13418 a1 = wxMax(1.0, a1);
13419 double a2 = log10(a1);
13420
13421 float cscale = scale_factor * a2 * 0.4;
13422
13423 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13424 dc.SetPen(*porange_pen);
13425 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13426 cscale);
13427 // Draw text, if enabled
13428
13429 if (bDrawCurrentValues) {
13430 dc.SetFont(*pTCFont);
13431 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13432 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13433 }
13434 }
13435 } // scale
13436 }
13437 /* This is useful for debugging the TC database
13438 else
13439 {
13440 dc.SetPen ( *porange_pen );
13441 dc.SetBrush ( *pgray_brush );
13442 dc.DrawPolygon ( 4, d );
13443 }
13444 */
13445 }
13446 lon_last = lon;
13447 lat_last = lat;
13448 }
13449 }
13450 }
13451}
13452
13453void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13454 pCwin = new TCWin(this, x, y, pvIDX);
13455}
13456
13457#define NUM_CURRENT_ARROW_POINTS 9
13458static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13459 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13460 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13461 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13462
13463void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13464 double scale) {
13465 if (scale > 1e-2) {
13466 float sin_rot = sin(rot_angle * PI / 180.);
13467 float cos_rot = cos(rot_angle * PI / 180.);
13468
13469 // Move to the first point
13470
13471 float xt = CurrentArrowArray[0].x;
13472 float yt = CurrentArrowArray[0].y;
13473
13474 float xp = (xt * cos_rot) - (yt * sin_rot);
13475 float yp = (xt * sin_rot) + (yt * cos_rot);
13476 int x1 = (int)(xp * scale);
13477 int y1 = (int)(yp * scale);
13478
13479 // Walk thru the point list
13480 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13481 xt = CurrentArrowArray[ip].x;
13482 yt = CurrentArrowArray[ip].y;
13483
13484 float xp = (xt * cos_rot) - (yt * sin_rot);
13485 float yp = (xt * sin_rot) + (yt * cos_rot);
13486 int x2 = (int)(xp * scale);
13487 int y2 = (int)(yp * scale);
13488
13489 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13490
13491 x1 = x2;
13492 y1 = y2;
13493 }
13494 }
13495}
13496
13497wxString ChartCanvas::FindValidUploadPort() {
13498 wxString port;
13499 // Try to use the saved persistent upload port first
13500 if (!g_uploadConnection.IsEmpty() &&
13501 g_uploadConnection.StartsWith(_T("Serial"))) {
13502 port = g_uploadConnection;
13503 }
13504
13505 else {
13506 // If there is no persistent upload port recorded (yet)
13507 // then use the first available serial connection which has output defined.
13508 for (auto *cp : TheConnectionParams()) {
13509 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13510 port << _T("Serial:") << cp->Port;
13511 }
13512 }
13513 return port;
13514}
13515
13516void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13517 if (!win) return;
13518
13519 if (NULL == g_pais_query_dialog_active) {
13520 int pos_x = g_ais_query_dialog_x;
13521 int pos_y = g_ais_query_dialog_y;
13522
13523 if (g_pais_query_dialog_active) {
13524 g_pais_query_dialog_active->Destroy();
13525 g_pais_query_dialog_active = new AISTargetQueryDialog();
13526 } else {
13527 g_pais_query_dialog_active = new AISTargetQueryDialog();
13528 }
13529
13530 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13531 wxPoint(pos_x, pos_y));
13532
13533 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13534 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13535 g_pais_query_dialog_active->SetMMSI(mmsi);
13536 g_pais_query_dialog_active->UpdateText();
13537 wxSize sz = g_pais_query_dialog_active->GetSize();
13538
13539 bool b_reset_pos = false;
13540#ifdef __WXMSW__
13541 // Support MultiMonitor setups which an allow negative window positions.
13542 // If the requested window title bar does not intersect any installed
13543 // monitor, then default to simple primary monitor positioning.
13544 RECT frame_title_rect;
13545 frame_title_rect.left = pos_x;
13546 frame_title_rect.top = pos_y;
13547 frame_title_rect.right = pos_x + sz.x;
13548 frame_title_rect.bottom = pos_y + 30;
13549
13550 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13551 b_reset_pos = true;
13552#else
13553
13554 // Make sure drag bar (title bar) of window intersects wxClient Area of
13555 // screen, with a little slop...
13556 wxRect window_title_rect; // conservative estimate
13557 window_title_rect.x = pos_x;
13558 window_title_rect.y = pos_y;
13559 window_title_rect.width = sz.x;
13560 window_title_rect.height = 30;
13561
13562 wxRect ClientRect = wxGetClientDisplayRect();
13563 ClientRect.Deflate(
13564 60, 60); // Prevent the new window from being too close to the edge
13565 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13566
13567#endif
13568
13569 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13570
13571 } else {
13572 g_pais_query_dialog_active->SetMMSI(mmsi);
13573 g_pais_query_dialog_active->UpdateText();
13574 }
13575
13576 g_pais_query_dialog_active->Show();
13577}
13578
13579void ChartCanvas::ToggleCanvasQuiltMode(void) {
13580 bool cur_mode = GetQuiltMode();
13581
13582 if (!GetQuiltMode())
13583 SetQuiltMode(true);
13584 else if (GetQuiltMode()) {
13585 SetQuiltMode(false);
13586 g_sticky_chart = GetQuiltReferenceChartIndex();
13587 }
13588
13589 if (cur_mode != GetQuiltMode()) {
13590 SetupCanvasQuiltMode();
13591 DoCanvasUpdate();
13592 InvalidateGL();
13593 Refresh();
13594 }
13595 // TODO What to do about this?
13596 // g_bQuiltEnable = GetQuiltMode();
13597
13598 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13599 if (ps52plib) ps52plib->GenerateStateHash();
13600
13601 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13602 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13603}
13604
13605void ChartCanvas::DoCanvasStackDelta(int direction) {
13606 if (!GetQuiltMode()) {
13607 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13608 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13609 if ((current_stack_index + direction) < 0) return;
13610
13611 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13612 int new_dbIndex =
13613 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13614
13615 if (IsChartQuiltableRef(new_dbIndex)) {
13616 ToggleCanvasQuiltMode();
13617 SelectQuiltRefdbChart(new_dbIndex);
13618 m_bpersistent_quilt = false;
13619 }
13620 } else {
13621 SelectChartFromStack(current_stack_index + direction);
13622 }
13623 } else {
13624 std::vector<int> piano_chart_index_array =
13625 GetQuiltExtendedStackdbIndexArray();
13626 int refdb = GetQuiltRefChartdbIndex();
13627
13628 // Find the ref chart in the stack
13629 int current_index = -1;
13630 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13631 if (refdb == piano_chart_index_array[i]) {
13632 current_index = i;
13633 break;
13634 }
13635 }
13636 if (current_index == -1) return;
13637
13638 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13639 int target_family = ctet.GetChartFamily();
13640
13641 int new_index = -1;
13642 int check_index = current_index + direction;
13643 bool found = false;
13644 int check_dbIndex = -1;
13645 int new_dbIndex = -1;
13646
13647 // When quilted. switch within the same chart family
13648 while (!found &&
13649 (unsigned int)check_index < piano_chart_index_array.size() &&
13650 (check_index >= 0)) {
13651 check_dbIndex = piano_chart_index_array[check_index];
13652 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13653 if (target_family == cte.GetChartFamily()) {
13654 found = true;
13655 new_index = check_index;
13656 new_dbIndex = check_dbIndex;
13657 break;
13658 }
13659
13660 check_index += direction;
13661 }
13662
13663 if (!found) return;
13664
13665 if (!IsChartQuiltableRef(new_dbIndex)) {
13666 ToggleCanvasQuiltMode();
13667 SelectdbChart(new_dbIndex);
13668 m_bpersistent_quilt = true;
13669 } else {
13670 SelectQuiltRefChart(new_index);
13671 }
13672 }
13673
13674 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13675 // (checkmarks etc)
13676 SetQuiltChartHiLiteIndex(-1);
13677
13678 ReloadVP();
13679}
13680
13681//--------------------------------------------------------------------------------------------------------
13682//
13683// Toolbar support
13684//
13685//--------------------------------------------------------------------------------------------------------
13686
13687void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13688 // Handle the per-canvas toolbar clicks here
13689
13690 switch (event.GetId()) {
13691 case ID_ZOOMIN: {
13692 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13693 break;
13694 }
13695
13696 case ID_ZOOMOUT: {
13697 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13698 break;
13699 }
13700
13701 case ID_STKUP:
13702 DoCanvasStackDelta(1);
13703 DoCanvasUpdate();
13704 break;
13705
13706 case ID_STKDN:
13707 DoCanvasStackDelta(-1);
13708 DoCanvasUpdate();
13709 break;
13710
13711 case ID_FOLLOW: {
13712 TogglebFollow();
13713 break;
13714 }
13715
13716 case ID_CURRENT: {
13717 ShowCurrents(!GetbShowCurrent());
13718 ReloadVP();
13719 Refresh(false);
13720 break;
13721 }
13722
13723 case ID_TIDE: {
13724 ShowTides(!GetbShowTide());
13725 ReloadVP();
13726 Refresh(false);
13727 break;
13728 }
13729
13730 case ID_ROUTE: {
13731 if (0 == m_routeState) {
13732 StartRoute();
13733 } else {
13734 FinishRoute();
13735 }
13736
13737#ifdef __ANDROID__
13738 androidSetRouteAnnunciator(m_routeState == 1);
13739#endif
13740 break;
13741 }
13742
13743 case ID_AIS: {
13744 SetAISCanvasDisplayStyle(-1);
13745 break;
13746 }
13747
13748 default:
13749 break;
13750 }
13751
13752 // And then let gFrame handle the rest....
13753 event.Skip();
13754}
13755
13756void ChartCanvas::SetShowAIS(bool show) {
13757 m_bShowAIS = show;
13758 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13759 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13760}
13761
13762void ChartCanvas::SetAttenAIS(bool show) {
13763 m_bShowAISScaled = show;
13764 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13765 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13766}
13767
13768void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
13769 // make some arrays to hold the dfferences between cycle steps
13770 // show all, scaled, hide all
13771 bool bShowAIS_Array[3] = {true, true, false};
13772 bool bShowScaled_Array[3] = {false, true, true};
13773 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
13774 _("Attenuate less critical AIS targets"),
13775 _("Hide AIS Targets")};
13776 wxString iconName_Array[3] = {_T("AIS"), _T("AIS_Suppressed"),
13777 _T("AIS_Disabled")};
13778 int ArraySize = 3;
13779 int AIS_Toolbar_Switch = 0;
13780 if (StyleIndx == -1) { // -1 means coming from toolbar button
13781 // find current state of switch
13782 for (int i = 1; i < ArraySize; i++) {
13783 if ((bShowAIS_Array[i] == m_bShowAIS) &&
13784 (bShowScaled_Array[i] == m_bShowAISScaled))
13785 AIS_Toolbar_Switch = i;
13786 }
13787 AIS_Toolbar_Switch++; // we did click so continu with next item
13788 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
13789 AIS_Toolbar_Switch++;
13790
13791 } else { // coming from menu bar.
13792 AIS_Toolbar_Switch = StyleIndx;
13793 }
13794 // make sure we are not above array
13795 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
13796
13797 int AIS_Toolbar_Switch_Next =
13798 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
13799 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
13800 AIS_Toolbar_Switch_Next++;
13801 if (AIS_Toolbar_Switch_Next >= ArraySize)
13802 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
13803
13804 // Set found values to global and member variables
13805 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
13806 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
13807}
13808
13809void ChartCanvas::TouchAISToolActive(void) {}
13810
13811void ChartCanvas::UpdateAISTBTool(void) {}
13812
13813//---------------------------------------------------------------------------------
13814//
13815// Compass/GPS status icon support
13816//
13817//---------------------------------------------------------------------------------
13818
13819void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
13820 // Look for change in overlap or positions
13821 bool b_update = false;
13822 int cc1_edge_comp = 2;
13823 wxRect rect = m_Compass->GetRect();
13824 wxSize parent_size = GetSize();
13825
13826 parent_size *= m_displayScale;
13827
13828 // check to see if it would overlap if it was in its home position (upper
13829 // right)
13830 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
13831 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
13832 wxRect compass_rect(compass_pt, rect.GetSize());
13833
13834 m_Compass->Move(compass_pt);
13835
13836 if (m_Compass && m_Compass->IsShown())
13837 m_Compass->UpdateStatus(b_force_new | b_update);
13838
13839 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
13840 scaler = wxMax(scaler, 1.0);
13841 wxPoint note_point = wxPoint(
13842 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
13843 m_notification_button->Move(note_point);
13844 m_notification_button->UpdateStatus();
13845
13846 if (b_force_new | b_update) Refresh();
13847}
13848
13849void ChartCanvas::SelectChartFromStack(int index, bool bDir,
13850 ChartTypeEnum New_Type,
13851 ChartFamilyEnum New_Family) {
13852 if (!GetpCurrentStack()) return;
13853 if (!ChartData) return;
13854
13855 if (index < GetpCurrentStack()->nEntry) {
13856 // Open the new chart
13857 ChartBase *pTentative_Chart;
13858 pTentative_Chart = ChartData->OpenStackChartConditional(
13859 GetpCurrentStack(), index, bDir, New_Type, New_Family);
13860
13861 if (pTentative_Chart) {
13862 if (m_singleChart) m_singleChart->Deactivate();
13863
13864 m_singleChart = pTentative_Chart;
13865 m_singleChart->Activate();
13866
13867 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13868 GetpCurrentStack(), m_singleChart->GetFullPath());
13869 }
13870
13871 // Setup the view
13872 double zLat, zLon;
13873 if (m_bFollow) {
13874 zLat = gLat;
13875 zLon = gLon;
13876 } else {
13877 zLat = m_vLat;
13878 zLon = m_vLon;
13879 }
13880
13881 double best_scale_ppm = GetBestVPScale(m_singleChart);
13882 double rotation = GetVPRotation();
13883 double oldskew = GetVPSkew();
13884 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
13885
13886 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
13887 if (fabs(oldskew) > 0.0001) rotation = 0.0;
13888 if (fabs(newskew) > 0.0001) rotation = newskew;
13889 }
13890
13891 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
13892
13893 UpdateGPSCompassStatusBox(true); // Pick up the rotation
13894 }
13895
13896 // refresh Piano
13897 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
13898 if (idx < 0) return;
13899
13900 std::vector<int> piano_active_chart_index_array;
13901 piano_active_chart_index_array.push_back(
13902 GetpCurrentStack()->GetCurrentEntrydbIndex());
13903 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
13904}
13905
13906void ChartCanvas::SelectdbChart(int dbindex) {
13907 if (!GetpCurrentStack()) return;
13908 if (!ChartData) return;
13909
13910 if (dbindex >= 0) {
13911 // Open the new chart
13912 ChartBase *pTentative_Chart;
13913 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
13914
13915 if (pTentative_Chart) {
13916 if (m_singleChart) m_singleChart->Deactivate();
13917
13918 m_singleChart = pTentative_Chart;
13919 m_singleChart->Activate();
13920
13921 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13922 GetpCurrentStack(), m_singleChart->GetFullPath());
13923 }
13924
13925 // Setup the view
13926 double zLat, zLon;
13927 if (m_bFollow) {
13928 zLat = gLat;
13929 zLon = gLon;
13930 } else {
13931 zLat = m_vLat;
13932 zLon = m_vLon;
13933 }
13934
13935 double best_scale_ppm = GetBestVPScale(m_singleChart);
13936
13937 if (m_singleChart)
13938 SetViewPoint(zLat, zLon, best_scale_ppm,
13939 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
13940
13941 // SetChartUpdatePeriod( );
13942
13943 // UpdateGPSCompassStatusBox(); // Pick up the rotation
13944 }
13945
13946 // TODO refresh_Piano();
13947}
13948
13949void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
13950 double target_scale = GetVP().view_scale_ppm;
13951
13952 if (!GetQuiltMode()) {
13953 if (GetpCurrentStack()) {
13954 int stack_index = -1;
13955 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
13956 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
13957 if (check_dbIndex < 0) continue;
13958 const ChartTableEntry &cte =
13959 ChartData->GetChartTableEntry(check_dbIndex);
13960 if (type == cte.GetChartType()) {
13961 stack_index = i;
13962 break;
13963 } else if (family == cte.GetChartFamily()) {
13964 stack_index = i;
13965 break;
13966 }
13967 }
13968
13969 if (stack_index >= 0) {
13970 SelectChartFromStack(stack_index);
13971 }
13972 }
13973 } else {
13974 int sel_dbIndex = -1;
13975 std::vector<int> piano_chart_index_array =
13976 GetQuiltExtendedStackdbIndexArray();
13977 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13978 int check_dbIndex = piano_chart_index_array[i];
13979 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13980 if (type == cte.GetChartType()) {
13981 if (IsChartQuiltableRef(check_dbIndex)) {
13982 sel_dbIndex = check_dbIndex;
13983 break;
13984 }
13985 } else if (family == cte.GetChartFamily()) {
13986 if (IsChartQuiltableRef(check_dbIndex)) {
13987 sel_dbIndex = check_dbIndex;
13988 break;
13989 }
13990 }
13991 }
13992
13993 if (sel_dbIndex >= 0) {
13994 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
13995 // Re-qualify the quilt reference chart selection
13996 AdjustQuiltRefChart();
13997 }
13998
13999 // Now reset the scale to the target...
14000 SetVPScale(target_scale);
14001 }
14002
14003 SetQuiltChartHiLiteIndex(-1);
14004
14005 ReloadVP();
14006}
14007
14008bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14009 return std::find(m_tile_yesshow_index_array.begin(),
14010 m_tile_yesshow_index_array.end(),
14011 index) != m_tile_yesshow_index_array.end();
14012}
14013
14014bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14015 return std::find(m_tile_noshow_index_array.begin(),
14016 m_tile_noshow_index_array.end(),
14017 index) != m_tile_noshow_index_array.end();
14018}
14019
14020void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14021 if (std::find(m_tile_noshow_index_array.begin(),
14022 m_tile_noshow_index_array.end(),
14023 index) == m_tile_noshow_index_array.end()) {
14024 m_tile_noshow_index_array.push_back(index);
14025 }
14026}
14027
14028//-------------------------------------------------------------------------------------------------------
14029//
14030// Piano support
14031//
14032//-------------------------------------------------------------------------------------------------------
14033
14034void ChartCanvas::HandlePianoClick(
14035 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14036 if (g_options && g_options->IsShown())
14037 return; // Piano might be invalid due to chartset updates.
14038 if (!m_pCurrentStack) return;
14039 if (!ChartData) return;
14040
14041 // stop movement or on slow computer we may get something like :
14042 // zoom out with the wheel (timer is set)
14043 // quickly click and display a chart, which may zoom in
14044 // but the delayed timer fires first and it zooms out again!
14045 StopMovement();
14046
14047 // When switching by piano key click, we may appoint the new target chart to
14048 // be any chart in the composite array.
14049 // As an improvement to UX, find the chart that is "closest" to the current
14050 // vp,
14051 // and select that chart. This will cause a jump to the centroid of that
14052 // chart
14053
14054 double distance = 25000; // RTW
14055 int closest_index = -1;
14056 for (int chart_index : selected_dbIndex_array) {
14057 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14058 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14059 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14060
14061 // measure distance as Manhattan style
14062 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14063 if (test_distance < distance) {
14064 distance = test_distance;
14065 closest_index = chart_index;
14066 }
14067 }
14068
14069 int selected_dbIndex = selected_dbIndex_array[0];
14070 if (closest_index >= 0) selected_dbIndex = closest_index;
14071
14072 if (!GetQuiltMode()) {
14073 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14074 if (IsChartQuiltableRef(selected_dbIndex)) {
14075 ToggleCanvasQuiltMode();
14076 SelectQuiltRefdbChart(selected_dbIndex);
14077 m_bpersistent_quilt = false;
14078 } else {
14079 SelectChartFromStack(selected_index);
14080 }
14081 } else {
14082 SelectChartFromStack(selected_index);
14083 g_sticky_chart = selected_dbIndex;
14084 }
14085
14086 if (m_singleChart)
14087 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14088 } else {
14089 // Handle MBTiles overlays first
14090 // Left click simply toggles the noshow array index entry
14091 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14092 bool bfound = false;
14093 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14094 if (m_tile_noshow_index_array[i] ==
14095 selected_dbIndex) { // chart is in the noshow list
14096 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14097 i); // erase it
14098 bfound = true;
14099 break;
14100 }
14101 }
14102 if (!bfound) {
14103 m_tile_noshow_index_array.push_back(selected_dbIndex);
14104 }
14105
14106 // If not already present, add this tileset to the "yes_show" array.
14107 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14108 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14109 }
14110
14111 else {
14112 if (IsChartQuiltableRef(selected_dbIndex)) {
14113 // if( ChartData ) ChartData->PurgeCache();
14114
14115 // If the chart is a vector chart, and of very large scale,
14116 // then we had better set the new scale directly to avoid excessive
14117 // underzoom on, eg, Inland ENCs
14118 bool set_scale = false;
14119 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14120 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14121 set_scale = true;
14122 }
14123 }
14124
14125 if (!set_scale) {
14126 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14127 } else {
14128 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14129
14130 // Adjust scale so that the selected chart is underzoomed/overzoomed
14131 // by a controlled amount
14132 ChartBase *pc =
14133 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14134 if (pc) {
14135 double proposed_scale_onscreen =
14137
14138 if (g_bPreserveScaleOnX) {
14139 proposed_scale_onscreen =
14140 wxMin(proposed_scale_onscreen,
14141 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14142 GetCanvasWidth()));
14143 } else {
14144 proposed_scale_onscreen =
14145 wxMin(proposed_scale_onscreen,
14146 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14147 GetCanvasWidth()));
14148
14149 proposed_scale_onscreen =
14150 wxMax(proposed_scale_onscreen,
14151 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14152 g_b_overzoom_x));
14153 }
14154
14155 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14156 }
14157 }
14158 } else {
14159 ToggleCanvasQuiltMode();
14160 SelectdbChart(selected_dbIndex);
14161 m_bpersistent_quilt = true;
14162 }
14163 }
14164 }
14165
14166 SetQuiltChartHiLiteIndex(-1);
14167 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14168 // (checkmarks etc)
14169 HideChartInfoWindow();
14170 DoCanvasUpdate();
14171 ReloadVP(); // Pick up the new selections
14172}
14173
14174void ChartCanvas::HandlePianoRClick(
14175 int x, int y, int selected_index,
14176 const std::vector<int> &selected_dbIndex_array) {
14177 if (g_options && g_options->IsShown())
14178 return; // Piano might be invalid due to chartset updates.
14179 if (!GetpCurrentStack()) return;
14180
14181 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14182 UpdateCanvasControlBar();
14183
14184 SetQuiltChartHiLiteIndex(-1);
14185}
14186
14187void ChartCanvas::HandlePianoRollover(
14188 int selected_index, const std::vector<int> &selected_dbIndex_array,
14189 int n_charts, int scale) {
14190 if (g_options && g_options->IsShown())
14191 return; // Piano might be invalid due to chartset updates.
14192 if (!GetpCurrentStack()) return;
14193 if (!ChartData) return;
14194
14195 if (ChartData->IsBusy()) return;
14196
14197 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14198
14199 if (!GetQuiltMode()) {
14200 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14201 } else {
14202 // Select the correct vector
14203 std::vector<int> piano_chart_index_array;
14204 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14205 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14206 if ((GetpCurrentStack()->nEntry > 1) ||
14207 (piano_chart_index_array.size() >= 1)) {
14208 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14209
14210 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14211 ReloadVP(false); // no VP adjustment allowed
14212 } else if (GetpCurrentStack()->nEntry == 1) {
14213 const ChartTableEntry &cte =
14214 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14215 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14216 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14217 ReloadVP(false);
14218 } else if ((-1 == selected_index) &&
14219 (0 == selected_dbIndex_array.size())) {
14220 ShowChartInfoWindow(key_location.x, -1);
14221 }
14222 }
14223 } else {
14224 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14225
14226 if ((GetpCurrentStack()->nEntry > 1) ||
14227 (piano_chart_index_array.size() >= 1)) {
14228 if (n_charts > 1)
14229 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14230 selected_dbIndex_array);
14231 else if (n_charts == 1)
14232 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14233
14234 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14235 ReloadVP(false); // no VP adjustment allowed
14236 }
14237 }
14238 }
14239}
14240
14241void ChartCanvas::ClearPianoRollover() {
14242 ClearQuiltChartHiLiteIndexArray();
14243 ShowChartInfoWindow(0, -1);
14244 std::vector<int> vec;
14245 ShowCompositeInfoWindow(0, 0, 0, vec);
14246 ReloadVP(false);
14247}
14248
14249void ChartCanvas::UpdateCanvasControlBar(void) {
14250 if (m_pianoFrozen) return;
14251
14252 if (!GetpCurrentStack()) return;
14253 if (!ChartData) return;
14254 if (!g_bShowChartBar) return;
14255
14256 int sel_type = -1;
14257 int sel_family = -1;
14258
14259 std::vector<int> piano_chart_index_array;
14260 std::vector<int> empty_piano_chart_index_array;
14261
14262 wxString old_hash = m_Piano->GetStoredHash();
14263
14264 if (GetQuiltMode()) {
14265 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14266 GetQuiltFullScreendbIndexArray());
14267
14268 std::vector<int> piano_active_chart_index_array =
14269 GetQuiltCandidatedbIndexArray();
14270 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14271
14272 std::vector<int> piano_eclipsed_chart_index_array =
14273 GetQuiltEclipsedStackdbIndexArray();
14274 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14275
14276 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14277 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14278
14279 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14280 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14281 } else {
14282 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14283 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14284 // TODO refresh_Piano();
14285
14286 if (m_singleChart) {
14287 sel_type = m_singleChart->GetChartType();
14288 sel_family = m_singleChart->GetChartFamily();
14289 }
14290 }
14291
14292 // Set up the TMerc and Skew arrays
14293 std::vector<int> piano_skew_chart_index_array;
14294 std::vector<int> piano_tmerc_chart_index_array;
14295 std::vector<int> piano_poly_chart_index_array;
14296
14297 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14298 const ChartTableEntry &ctei =
14299 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14300 double skew_norm = ctei.GetChartSkew();
14301 if (skew_norm > 180.) skew_norm -= 360.;
14302
14303 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14304 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14305
14306 // Polyconic skewed charts should show as skewed
14307 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14308 if (fabs(skew_norm) > 1.)
14309 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14310 else
14311 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14312 } else if (fabs(skew_norm) > 1.)
14313 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14314 }
14315 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14316 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14317 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14318
14319 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14320 if (new_hash != old_hash) {
14321 m_Piano->FormatKeys();
14322 HideChartInfoWindow();
14323 m_Piano->ResetRollover();
14324 SetQuiltChartHiLiteIndex(-1);
14325 m_brepaint_piano = true;
14326 }
14327
14328 // Create a bitmask int that describes what Family/Type of charts are shown in
14329 // the bar, and notify the platform.
14330 int mask = 0;
14331 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14332 const ChartTableEntry &ctei =
14333 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14334 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14335 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14336 if (e == CHART_FAMILY_RASTER) mask |= 1;
14337 if (e == CHART_FAMILY_VECTOR) {
14338 if (t == CHART_TYPE_CM93COMP)
14339 mask |= 4;
14340 else
14341 mask |= 2;
14342 }
14343 }
14344
14345 wxString s_indicated;
14346 if (sel_type == CHART_TYPE_CM93COMP)
14347 s_indicated = _T("cm93");
14348 else {
14349 if (sel_family == CHART_FAMILY_RASTER)
14350 s_indicated = _T("raster");
14351 else if (sel_family == CHART_FAMILY_VECTOR)
14352 s_indicated = _T("vector");
14353 }
14354
14355 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14356}
14357
14358void ChartCanvas::FormatPianoKeys(void) { m_Piano->FormatKeys(); }
14359
14360void ChartCanvas::PianoPopupMenu(
14361 int x, int y, int selected_index,
14362 const std::vector<int> &selected_dbIndex_array) {
14363 if (!GetpCurrentStack()) return;
14364
14365 // No context menu if quilting is disabled
14366 if (!GetQuiltMode()) return;
14367
14368 m_piano_ctx_menu = new wxMenu();
14369
14370 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14371 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14372 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14373 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14374 } else {
14375 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14376 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14377 // wxEVT_COMMAND_MENU_SELECTED,
14378 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14379
14380 menu_selected_dbIndex = selected_dbIndex_array[0];
14381 menu_selected_index = selected_index;
14382
14383 // Search the no-show array
14384 bool b_is_in_noshow = false;
14385 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14386 if (m_quilt_noshow_index_array[i] ==
14387 menu_selected_dbIndex) // chart is in the noshow list
14388 {
14389 b_is_in_noshow = true;
14390 break;
14391 }
14392 }
14393
14394 if (b_is_in_noshow) {
14395 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14396 _("Show This Chart"));
14397 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14398 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14399 } else if (GetpCurrentStack()->nEntry > 1) {
14400 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14401 _("Hide This Chart"));
14402 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14403 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14404 }
14405 }
14406
14407 wxPoint pos = wxPoint(x, y - 30);
14408
14409 // Invoke the drop-down menu
14410 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14411 PopupMenu(m_piano_ctx_menu, pos);
14412
14413 delete m_piano_ctx_menu;
14414 m_piano_ctx_menu = NULL;
14415
14416 HideChartInfoWindow();
14417 m_Piano->ResetRollover();
14418
14419 SetQuiltChartHiLiteIndex(-1);
14420 ClearQuiltChartHiLiteIndexArray();
14421
14422 ReloadVP();
14423}
14424
14425void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14426 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14427 if (m_quilt_noshow_index_array[i] ==
14428 menu_selected_dbIndex) // chart is in the noshow list
14429 {
14430 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14431 break;
14432 }
14433 }
14434}
14435
14436void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14437 if (!GetpCurrentStack()) return;
14438 if (!ChartData) return;
14439
14440 RemoveChartFromQuilt(menu_selected_dbIndex);
14441
14442 // It could happen that the chart being disabled is the reference
14443 // chart....
14444 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14445 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14446
14447 int i = menu_selected_index + 1; // select next smaller scale chart
14448 bool b_success = false;
14449 while (i < GetpCurrentStack()->nEntry - 1) {
14450 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14451 if (type == ChartData->GetDBChartType(dbIndex)) {
14452 SelectQuiltRefChart(i);
14453 b_success = true;
14454 break;
14455 }
14456 i++;
14457 }
14458
14459 // If that did not work, try to select the next larger scale compatible
14460 // chart
14461 if (!b_success) {
14462 i = menu_selected_index - 1;
14463 while (i > 0) {
14464 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14465 if (type == ChartData->GetDBChartType(dbIndex)) {
14466 SelectQuiltRefChart(i);
14467 b_success = true;
14468 break;
14469 }
14470 i--;
14471 }
14472 }
14473 }
14474}
14475
14476void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14477 // Remove the item from the list (if it appears) to avoid multiple addition
14478 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14479 if (m_quilt_noshow_index_array[i] ==
14480 dbIndex) // chart is already in the noshow list
14481 {
14482 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14483 break;
14484 }
14485 }
14486
14487 m_quilt_noshow_index_array.push_back(dbIndex);
14488}
14489
14490bool ChartCanvas::UpdateS52State() {
14491 bool retval = false;
14492 // printf(" update %d\n", IsPrimaryCanvas());
14493
14494 if (ps52plib) {
14495 ps52plib->SetShowS57Text(m_encShowText);
14496 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14497 ps52plib->m_bShowSoundg = m_encShowDepth;
14498 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14499 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14500
14501 // Lights
14502 if (!m_encShowLights) // On, going off
14503 ps52plib->AddObjNoshow("LIGHTS");
14504 else // Off, going on
14505 ps52plib->RemoveObjNoshow("LIGHTS");
14506 ps52plib->SetLightsOff(!m_encShowLights);
14507 ps52plib->m_bExtendLightSectors = true;
14508
14509 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14510 ps52plib->SetAnchorOn(m_encShowAnchor);
14511 ps52plib->SetQualityOfData(m_encShowDataQual);
14512 }
14513
14514 return retval;
14515}
14516
14517void ChartCanvas::SetShowENCDataQual(bool show) {
14518 m_encShowDataQual = show;
14519 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14520 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14521
14522 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14523}
14524
14525void ChartCanvas::SetShowENCText(bool show) {
14526 m_encShowText = show;
14527 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14528 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14529
14530 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14531}
14532
14533void ChartCanvas::SetENCDisplayCategory(int category) {
14534 m_encDisplayCategory = category;
14535 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14536}
14537
14538void ChartCanvas::SetShowENCDepth(bool show) {
14539 m_encShowDepth = show;
14540 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14541 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14542
14543 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14544}
14545
14546void ChartCanvas::SetShowENCLightDesc(bool show) {
14547 m_encShowLightDesc = show;
14548 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14549 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14550
14551 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14552}
14553
14554void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14555 m_encShowBuoyLabels = show;
14556 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14557}
14558
14559void ChartCanvas::SetShowENCLights(bool show) {
14560 m_encShowLights = show;
14561 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14562 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14563
14564 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14565}
14566
14567void ChartCanvas::SetShowENCAnchor(bool show) {
14568 m_encShowAnchor = show;
14569 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14570 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14571
14572 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14573}
14574
14575wxRect ChartCanvas::GetMUIBarRect() {
14576 wxRect rv;
14577 if (m_muiBar) {
14578 rv = m_muiBar->GetRect();
14579 }
14580
14581 return rv;
14582}
14583
14584void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14585 if (!GetAlertString().IsEmpty()) {
14586 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14587 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14588
14589 dc.SetFont(*pfont);
14590 dc.SetPen(*wxTRANSPARENT_PEN);
14591
14592 dc.SetBrush(wxColour(243, 229, 47));
14593 int w, h;
14594 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14595 h += 2;
14596 // int yp = vp.pix_height - 20 - h;
14597
14598 wxRect sbr = GetScaleBarRect();
14599 int xp = sbr.x + sbr.width + 10;
14600 int yp = (sbr.y + sbr.height) - h;
14601
14602 int wdraw = w + 10;
14603 dc.DrawRectangle(xp, yp, wdraw, h);
14604 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14605 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14606 }
14607}
14608
14609//--------------------------------------------------------------------------------------------------------
14610// Screen Brightness Control Support Routines
14611//
14612//--------------------------------------------------------------------------------------------------------
14613
14614#ifdef __UNIX__
14615#define BRIGHT_XCALIB
14616#define __OPCPN_USEICC__
14617#endif
14618
14619#ifdef __OPCPN_USEICC__
14620int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14621 double co_green, double co_blue);
14622
14623wxString temp_file_name;
14624#endif
14625
14626#if 0
14627class ocpnCurtain: public wxDialog
14628{
14629 DECLARE_CLASS( ocpnCurtain )
14630 DECLARE_EVENT_TABLE()
14631
14632public:
14633 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14634 ~ocpnCurtain( );
14635 bool ProcessEvent(wxEvent& event);
14636
14637};
14638
14639IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14640
14641BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14642END_EVENT_TABLE()
14643
14644ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14645{
14646 wxDialog::Create( parent, -1, _T("ocpnCurtain"), position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14647}
14648
14649ocpnCurtain::~ocpnCurtain()
14650{
14651}
14652
14653bool ocpnCurtain::ProcessEvent(wxEvent& event)
14654{
14655 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14656 return GetParent()->GetEventHandler()->ProcessEvent(event);
14657}
14658#endif
14659
14660#ifdef _WIN32
14661#include <windows.h>
14662
14663HMODULE hGDI32DLL;
14664typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14665typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14666SetDeviceGammaRamp_ptr_type
14667 g_pSetDeviceGammaRamp; // the API entry points in the dll
14668GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14669
14670WORD *g_pSavedGammaMap;
14671
14672#endif
14673
14674int InitScreenBrightness(void) {
14675#ifdef _WIN32
14676#ifdef ocpnUSE_GL
14677 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14678 HDC hDC;
14679 BOOL bbr;
14680
14681 if (NULL == hGDI32DLL) {
14682 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14683
14684 if (NULL != hGDI32DLL) {
14685 // Get the entry points of the required functions
14686 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14687 hGDI32DLL, "SetDeviceGammaRamp");
14688 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14689 hGDI32DLL, "GetDeviceGammaRamp");
14690
14691 // If the functions are not found, unload the DLL and return false
14692 if ((NULL == g_pSetDeviceGammaRamp) ||
14693 (NULL == g_pGetDeviceGammaRamp)) {
14694 FreeLibrary(hGDI32DLL);
14695 hGDI32DLL = NULL;
14696 return 0;
14697 }
14698 }
14699 }
14700
14701 // Interface is ready, so....
14702 // Get some storage
14703 if (!g_pSavedGammaMap) {
14704 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14705
14706 hDC = GetDC(NULL); // Get the full screen DC
14707 bbr = g_pGetDeviceGammaRamp(
14708 hDC, g_pSavedGammaMap); // Get the existing ramp table
14709 ReleaseDC(NULL, hDC); // Release the DC
14710 }
14711
14712 // On Windows hosts, try to adjust the registry to allow full range
14713 // setting of Gamma table This is an undocumented Windows hack.....
14714 wxRegKey *pRegKey = new wxRegKey(
14715 _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows ")
14716 _T("NT\\CurrentVersion\\ICM"));
14717 if (!pRegKey->Exists()) pRegKey->Create();
14718 pRegKey->SetValue(_T("GdiIcmGammaRange"), 256);
14719
14720 g_brightness_init = true;
14721 return 1;
14722 }
14723#endif
14724
14725 {
14726 if (NULL == g_pcurtain) {
14727 if (gFrame->CanSetTransparent()) {
14728 // Build the curtain window
14729 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, _T(""),
14730 wxPoint(0, 0), ::wxGetDisplaySize(),
14731 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14732 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14733
14734 // g_pcurtain = new ocpnCurtain(gFrame,
14735 // wxPoint(0,0),::wxGetDisplaySize(),
14736 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14737 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14738
14739 g_pcurtain->Hide();
14740
14741 HWND hWnd = GetHwndOf(g_pcurtain);
14742 SetWindowLong(hWnd, GWL_EXSTYLE,
14743 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14744 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14745 g_pcurtain->SetTransparent(0);
14746
14747 g_pcurtain->Maximize();
14748 g_pcurtain->Show();
14749
14750 // All of this is obtuse, but necessary for Windows...
14751 g_pcurtain->Enable();
14752 g_pcurtain->Disable();
14753
14754 gFrame->Disable();
14755 gFrame->Enable();
14756 // SetFocus();
14757 }
14758 }
14759 g_brightness_init = true;
14760
14761 return 1;
14762 }
14763#else
14764 // Look for "xcalib" application
14765 wxString cmd(_T ( "xcalib -version" ));
14766
14767 wxArrayString output;
14768 long r = wxExecute(cmd, output);
14769 if (0 != r)
14770 wxLogMessage(
14771 _T(" External application \"xcalib\" not found. Screen brightness ")
14772 _T("not changed."));
14773
14774 g_brightness_init = true;
14775 return 0;
14776#endif
14777}
14778
14779int RestoreScreenBrightness(void) {
14780#ifdef _WIN32
14781
14782 if (g_pSavedGammaMap) {
14783 HDC hDC = GetDC(NULL); // Get the full screen DC
14784 g_pSetDeviceGammaRamp(hDC,
14785 g_pSavedGammaMap); // Restore the saved ramp table
14786 ReleaseDC(NULL, hDC); // Release the DC
14787
14788 free(g_pSavedGammaMap);
14789 g_pSavedGammaMap = NULL;
14790 }
14791
14792 if (g_pcurtain) {
14793 g_pcurtain->Close();
14794 g_pcurtain->Destroy();
14795 g_pcurtain = NULL;
14796 }
14797
14798 g_brightness_init = false;
14799 return 1;
14800
14801#endif
14802
14803#ifdef BRIGHT_XCALIB
14804 if (g_brightness_init) {
14805 wxString cmd;
14806 cmd = _T("xcalib -clear");
14807 wxExecute(cmd, wxEXEC_ASYNC);
14808 g_brightness_init = false;
14809 }
14810
14811 return 1;
14812#endif
14813
14814 return 0;
14815}
14816
14817// Set brightness. [0..100]
14818int SetScreenBrightness(int brightness) {
14819#ifdef _WIN32
14820
14821 // Under Windows, we use the SetDeviceGammaRamp function which exists in
14822 // some (most modern?) versions of gdi32.dll Load the required library dll,
14823 // if not already in place
14824#ifdef ocpnUSE_GL
14825 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14826 if (g_pcurtain) {
14827 g_pcurtain->Close();
14828 g_pcurtain->Destroy();
14829 g_pcurtain = NULL;
14830 }
14831
14832 InitScreenBrightness();
14833
14834 if (NULL == hGDI32DLL) {
14835 // Unicode stuff.....
14836 wchar_t wdll_name[80];
14837 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
14838 LPCWSTR cstr = wdll_name;
14839
14840 hGDI32DLL = LoadLibrary(cstr);
14841
14842 if (NULL != hGDI32DLL) {
14843 // Get the entry points of the required functions
14844 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14845 hGDI32DLL, "SetDeviceGammaRamp");
14846 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14847 hGDI32DLL, "GetDeviceGammaRamp");
14848
14849 // If the functions are not found, unload the DLL and return false
14850 if ((NULL == g_pSetDeviceGammaRamp) ||
14851 (NULL == g_pGetDeviceGammaRamp)) {
14852 FreeLibrary(hGDI32DLL);
14853 hGDI32DLL = NULL;
14854 return 0;
14855 }
14856 }
14857 }
14858
14859 HDC hDC = GetDC(NULL); // Get the full screen DC
14860
14861 /*
14862 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
14863 if (cmcap != CM_GAMMA_RAMP)
14864 {
14865 wxLogMessage(_T(" Video hardware does not support brightness control by
14866 gamma ramp adjustment.")); return false;
14867 }
14868 */
14869
14870 int increment = brightness * 256 / 100;
14871
14872 // Build the Gamma Ramp table
14873 WORD GammaTable[3][256];
14874
14875 int table_val = 0;
14876 for (int i = 0; i < 256; i++) {
14877 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
14878 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
14879 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
14880
14881 table_val += increment;
14882
14883 if (table_val > 65535) table_val = 65535;
14884 }
14885
14886 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
14887 ReleaseDC(NULL, hDC); // Release the DC
14888
14889 return 1;
14890 }
14891#endif
14892
14893 {
14894 if (g_pSavedGammaMap) {
14895 HDC hDC = GetDC(NULL); // Get the full screen DC
14896 g_pSetDeviceGammaRamp(hDC,
14897 g_pSavedGammaMap); // Restore the saved ramp table
14898 ReleaseDC(NULL, hDC); // Release the DC
14899 }
14900
14901 if (brightness < 100) {
14902 if (NULL == g_pcurtain) InitScreenBrightness();
14903
14904 if (g_pcurtain) {
14905 int sbrite = wxMax(1, brightness);
14906 sbrite = wxMin(100, sbrite);
14907
14908 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
14909 }
14910 } else {
14911 if (g_pcurtain) {
14912 g_pcurtain->Close();
14913 g_pcurtain->Destroy();
14914 g_pcurtain = NULL;
14915 }
14916 }
14917
14918 return 1;
14919 }
14920
14921#endif
14922
14923#ifdef BRIGHT_XCALIB
14924
14925 if (!g_brightness_init) {
14926 last_brightness = 100;
14927 g_brightness_init = true;
14928 temp_file_name = wxFileName::CreateTempFileName(_T(""));
14929 InitScreenBrightness();
14930 }
14931
14932#ifdef __OPCPN_USEICC__
14933 // Create a dead simple temporary ICC profile file, with gamma ramps set as
14934 // desired, and then activate this temporary profile using xcalib <filename>
14935 if (!CreateSimpleICCProfileFile(
14936 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
14937 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
14938 wxString cmd(_T ( "xcalib " ));
14939 cmd += temp_file_name;
14940
14941 wxExecute(cmd, wxEXEC_ASYNC);
14942 }
14943
14944#else
14945 // Or, use "xcalib -co" to set overall contrast value
14946 // This is not as nice, since the -co parameter wants to be a fraction of
14947 // the current contrast, and values greater than 100 are not allowed. As a
14948 // result, increases of contrast must do a "-clear" step first, which
14949 // produces objectionable flashing.
14950 if (brightness > last_brightness) {
14951 wxString cmd;
14952 cmd = _T("xcalib -clear");
14953 wxExecute(cmd, wxEXEC_ASYNC);
14954
14955 ::wxMilliSleep(10);
14956
14957 int brite_adj = wxMax(1, brightness);
14958 cmd.Printf(_T("xcalib -co %2d -a"), brite_adj);
14959 wxExecute(cmd, wxEXEC_ASYNC);
14960 } else {
14961 int brite_adj = wxMax(1, brightness);
14962 int factor = (brite_adj * 100) / last_brightness;
14963 factor = wxMax(1, factor);
14964 wxString cmd;
14965 cmd.Printf(_T("xcalib -co %2d -a"), factor);
14966 wxExecute(cmd, wxEXEC_ASYNC);
14967 }
14968
14969#endif
14970
14971 last_brightness = brightness;
14972
14973#endif
14974
14975 return 0;
14976}
14977
14978#ifdef __OPCPN_USEICC__
14979
14980#define MLUT_TAG 0x6d4c5554L
14981#define VCGT_TAG 0x76636774L
14982
14983int GetIntEndian(unsigned char *s) {
14984 int ret;
14985 unsigned char *p;
14986 int i;
14987
14988 p = (unsigned char *)&ret;
14989
14990 if (1)
14991 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
14992 else
14993 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
14994
14995 return ret;
14996}
14997
14998unsigned short GetShortEndian(unsigned char *s) {
14999 unsigned short ret;
15000 unsigned char *p;
15001 int i;
15002
15003 p = (unsigned char *)&ret;
15004
15005 if (1)
15006 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15007 else
15008 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15009
15010 return ret;
15011}
15012
15013// Create a very simple Gamma correction file readable by xcalib
15014int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15015 double co_green, double co_blue) {
15016 FILE *fp;
15017
15018 if (file_name) {
15019 fp = fopen(file_name, "wb");
15020 if (!fp) return -1; /* file can not be created */
15021 } else
15022 return -1; /* filename char pointer not valid */
15023
15024 // Write header
15025 char header[128];
15026 for (int i = 0; i < 128; i++) header[i] = 0;
15027
15028 fwrite(header, 128, 1, fp);
15029
15030 // Num tags
15031 int numTags0 = 1;
15032 int numTags = GetIntEndian((unsigned char *)&numTags0);
15033 fwrite(&numTags, 1, 4, fp);
15034
15035 int tagName0 = VCGT_TAG;
15036 int tagName = GetIntEndian((unsigned char *)&tagName0);
15037 fwrite(&tagName, 1, 4, fp);
15038
15039 int tagOffset0 = 128 + 4 * sizeof(int);
15040 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15041 fwrite(&tagOffset, 1, 4, fp);
15042
15043 int tagSize0 = 1;
15044 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15045 fwrite(&tagSize, 1, 4, fp);
15046
15047 fwrite(&tagName, 1, 4, fp); // another copy of tag
15048
15049 fwrite(&tagName, 1, 4, fp); // dummy
15050
15051 // Table type
15052
15053 /* VideoCardGammaTable (The simplest type) */
15054 int gammatype0 = 0;
15055 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15056 fwrite(&gammatype, 1, 4, fp);
15057
15058 int numChannels0 = 3;
15059 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15060 fwrite(&numChannels, 1, 2, fp);
15061
15062 int numEntries0 = 256;
15063 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15064 fwrite(&numEntries, 1, 2, fp);
15065
15066 int entrySize0 = 1;
15067 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15068 fwrite(&entrySize, 1, 2, fp);
15069
15070 unsigned char ramp[256];
15071
15072 // Red ramp
15073 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15074 fwrite(ramp, 256, 1, fp);
15075
15076 // Green ramp
15077 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15078 fwrite(ramp, 256, 1, fp);
15079
15080 // Blue ramp
15081 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15082 fwrite(ramp, 256, 1, fp);
15083
15084 fclose(fp);
15085
15086 return 0;
15087}
15088#endif // __OPCPN_USEICC__
bool g_bresponsive
Flag to control adaptive UI scaling.
Definition ocpn_app.cpp:662
Global state for AIS decoder.
Dialog for displaying a list of AIS targets.
Dialog for querying detailed information about an AIS target.
bool Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &caption=_("Object Query"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=AIS_TARGET_QUERY_STYLE)
Creation.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
Represents an active track that is currently being recorded.
Definition track.h:221
Handles context menu events for the chart canvas.
Definition canvasMenu.h:82
A custom panel for displaying chart information.
Definition ChInfoWin.h:36
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:131
Base class for all chart types.
Definition chartbase.h:119
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:151
bool GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates rounded to nearest integer using specified vie...
Definition chcanv.cpp:4571
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4567
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3666
void GetDoubleCanvasPointPixVP(ViewPort &vp, double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision,...
Definition chcanv.cpp:4517
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:745
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:475
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:506
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2403
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7573
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5070
float GetVPScale()
Return the ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:464
double GetCanvasTrueScale()
Return the physical pixels per meter at chart center, accounting for latitude distortion.
Definition chcanv.h:480
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4648
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5349
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:729
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4592
void ZoomCanvas(double factor, bool can_zoom_to_cursor=true, bool stoptimer=true)
Perform a smooth zoom operation on the chart canvas by the specified factor.
Definition chcanv.cpp:4654
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4512
bool SetViewPoint(double lat, double lon)
Set the viewport center point.
Definition chcanv.cpp:5368
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:9921
Manages the chart database and provides access to chart data.
Definition chartdb.h:95
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:464
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:392
Primary navigation console display for route and vessel tracking.
Definition concanv.h:127
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition FontMgr.cpp: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:1709
Represents a waypoint or mark within the navigation system.
Definition route_point.h:70
wxString m_MarkDescription
Description text for the waypoint.
LLBBox m_wpBBox
Bounding box for the waypoint.
bool m_bRPIsBeingEdited
Flag indicating if this waypoint is currently being edited.
wxRect CurrentRect_in_DC
Current rectangle occupied by the waypoint in the display.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bIsActive
Flag indicating if this waypoint is active for navigation.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
double m_seg_len
Length of the leg from previous waypoint to this waypoint in nautical miles.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
bool m_bBlink
Flag indicating if the waypoint should blink when displayed.
bool m_bPtIsSelected
Flag indicating if this waypoint is currently selected.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
Represents a navigational route in the navigation system.
Definition route.h:98
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:202
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:335
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:236
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:207
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:287
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:246
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:277
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:297
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:223
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:314
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:194
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp: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
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.