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/navutil_base.h"
54#include "model/own_ship.h"
55#include "model/plugin_comm.h"
56#include "model/route.h"
57#include "model/routeman.h"
58#include "model/select.h"
59#include "model/select_item.h"
60#include "model/track.h"
61#include "model/wx28compat.h"
62
63#include "ais.h"
64#include "AISTargetAlertDialog.h"
65#include "CanvasConfig.h"
66#include "canvasMenu.h"
67#include "CanvasOptions.h"
68#include "chartdb.h"
69#include "chartimg.h"
70#include "chcanv.h"
71#include "ChInfoWin.h"
72#include "cm93.h" // for chart outline draw
73#include "compass.h"
74#include "concanv.h"
75#include "displays.h"
76#include "hotkeys_dlg.h"
77#include "FontMgr.h"
78#include "glTextureDescriptor.h"
79#include "gshhs.h"
80#include "iENCToolbar.h"
81#include "kml.h"
82#include "line_clip.h"
83#include "MarkInfo.h"
84#include "mbtiles.h"
85#include "MUIBar.h"
86#include "navutil.h"
87#include "OCPN_AUIManager.h"
88#include "ocpndc.h"
89#include "ocpn_frame.h"
90#include "ocpn_pixel.h"
91#include "OCPNRegion.h"
92#include "options.h"
93#include "piano.h"
94#include "pluginmanager.h"
95#include "Quilt.h"
96#include "route_gui.h"
97#include "routemanagerdialog.h"
98#include "route_point_gui.h"
99#include "route_validator.h"
100#include "RoutePropDlgImpl.h"
101#include "s52plib.h"
102#include "s52utils.h"
103#include "s57chart.h" // for ArrayOfS57Obj
104#include "SendToGpsDlg.h"
105#include "shapefile_basemap.h"
106#include "styles.h"
107#include "SystemCmdSound.h"
108#include "tcmgr.h"
109#include "TCWin.h"
110#include "thumbwin.h"
111#include "tide_time.h"
112#include "timers.h"
113#include "toolbar.h"
114#include "track_gui.h"
115#include "TrackPropDlg.h"
116#include "undo.h"
117
118#include "s57_ocpn_utils.h"
119
120#ifdef __ANDROID__
121#include "androidUTIL.h"
122#endif
123
124#ifdef ocpnUSE_GL
125#include "glChartCanvas.h"
126#include "notification_manager_gui.h"
128#endif
129
130#ifdef __VISUALC__
131#include <wx/msw/msvcrt.h>
132#endif
133
134#ifndef __WXMSW__
135#include <signal.h>
136#include <setjmp.h>
137
138#endif
139
140extern float g_ShipScaleFactorExp;
141extern double g_mouse_zoom_sensitivity;
142
143#include <vector>
144
145#ifdef __WXMSW__
146#define printf printf2
147
148int __cdecl printf2(const char *format, ...) {
149 char str[1024];
150
151 va_list argptr;
152 va_start(argptr, format);
153 int ret = vsnprintf(str, sizeof(str), format, argptr);
154 va_end(argptr);
155 OutputDebugStringA(str);
156 return ret;
157}
158#endif
159
160#if defined(__MSVC__) && (_MSC_VER < 1700)
161#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
162#endif
163
164// Define to enable the invocation of a temporary menubar by pressing the Alt
165// key. Not implemented for Windows XP, as it interferes with Alt-Tab
166// processing.
167#define OCPN_ALT_MENUBAR 1
168
169// Profiling support
170// #include "/usr/include/valgrind/callgrind.h"
171
172// ----------------------------------------------------------------------------
173// Useful Prototypes
174// ----------------------------------------------------------------------------
175extern bool G_FloatPtInPolygon(MyFlPoint *rgpts, int wnumpts, float x, float y);
176extern void catch_signals(int signo);
177
178extern void AlphaBlending(ocpnDC &dc, int x, int y, int size_x, int size_y,
179 float radius, wxColour color,
180 unsigned char transparency);
181
182extern double g_ChartNotRenderScaleFactor;
183extern ChartDB *ChartData;
184extern bool bDBUpdateInProgress;
185extern ColorScheme global_color_scheme;
186extern int g_nbrightness;
187
188extern ConsoleCanvas *console;
189extern OCPNPlatform *g_Platform;
190
191extern RouteList *pRouteList;
192extern std::vector<Track *> g_TrackList;
193extern MyConfig *pConfig;
194extern Routeman *g_pRouteMan;
195extern ThumbWin *pthumbwin;
196extern TCMgr *ptcmgr;
197extern Select *pSelectTC;
198extern MarkInfoDlg *g_pMarkInfoDialog;
199extern RoutePropDlgImpl *pRoutePropDialog;
200extern TrackPropDlg *pTrackPropDialog;
201extern ActiveTrack *g_pActiveTrack;
202
203extern double AnchorPointMinDist;
204extern bool AnchorAlertOn1;
205extern bool AnchorAlertOn2;
206extern int g_nAWMax;
207
208extern RouteManagerDialog *pRouteManagerDialog;
209extern GoToPositionDialog *pGoToPositionDialog;
210extern wxString GetLayerName(int id);
211extern wxString g_uploadConnection;
212extern bool g_bsimplifiedScalebar;
213
214extern bool bDrawCurrentValues;
215
216extern s52plib *ps52plib;
217
218extern bool g_bTempShowMenuBar;
219extern bool g_bShowMenuBar;
220extern bool g_bShowCompassWin;
221
222extern MyFrame *gFrame;
223extern options *g_options;
224
225extern int g_iNavAidRadarRingsNumberVisible;
226extern bool g_bNavAidRadarRingsShown;
227extern float g_fNavAidRadarRingsStep;
228extern int g_pNavAidRadarRingsStepUnits;
229extern bool g_bWayPointPreventDragging;
230extern bool g_bEnableZoomToCursor;
231extern bool g_bShowChartBar;
232extern int g_ENCSoundingScaleFactor;
233extern int g_ENCTextScaleFactor;
234extern int g_maxzoomin;
235
236bool g_bShowShipToActive;
237int g_shipToActiveStyle;
238int g_shipToActiveColor;
239
240extern AISTargetQueryDialog *g_pais_query_dialog_active;
241
242extern int g_S57_dialog_sx, g_S57_dialog_sy;
243
244extern PopUpDSlide *pPopupDetailSlider;
245extern int g_detailslider_dialog_x, g_detailslider_dialog_y;
246
247extern bool g_b_overzoom_x; // Allow high overzoom
248extern double g_plus_minus_zoom_factor;
249
250extern int g_OwnShipIconType;
251extern double g_n_ownship_length_meters;
252extern double g_n_ownship_beam_meters;
253extern double g_n_gps_antenna_offset_y;
254extern double g_n_gps_antenna_offset_x;
255extern int g_n_ownship_min_mm;
256
257extern double g_COGAvg; // only needed for debug....
258
259extern int g_click_stop;
260
261extern double g_ownship_predictor_minutes;
262extern int g_cog_predictor_style;
263extern wxString g_cog_predictor_color;
264extern int g_cog_predictor_endmarker;
265extern int g_ownship_HDTpredictor_style;
266extern wxString g_ownship_HDTpredictor_color;
267extern int g_ownship_HDTpredictor_endmarker;
268extern int g_ownship_HDTpredictor_width;
269extern double g_ownship_HDTpredictor_miles;
270
271extern bool g_bquiting;
272extern AISTargetListDialog *g_pAISTargetList;
273
274extern PlugInManager *g_pi_manager;
275
276extern OCPN_AUIManager *g_pauimgr;
277
278extern bool g_bopengl;
279
280extern bool g_bFullScreenQuilt;
281
282extern bool g_bsmoothpanzoom;
283extern bool g_bSmoothRecenter;
284
285bool g_bDebugOGL;
286
287extern bool g_b_assume_azerty;
288
289extern ChartGroupArray *g_pGroupArray;
290
291extern S57QueryDialog *g_pObjectQueryDialog;
292extern ocpnStyle::StyleManager *g_StyleManager;
293
294extern OcpnSound *g_anchorwatch_sound;
295
296extern bool g_bresponsive;
297extern int g_chart_zoom_modifier_raster;
298extern int g_chart_zoom_modifier_vector;
299extern int g_ChartScaleFactor;
300
301#ifdef ocpnUSE_GL
302#endif
303
304extern double g_gl_ms_per_frame;
305extern bool g_benable_rotate;
306extern bool g_bRollover;
307
308extern bool g_bSpaceDropMark;
309extern bool g_bAutoHideToolbar;
310extern int g_nAutoHideToolbar;
311extern bool g_bDeferredInitDone;
312
313extern wxString g_CmdSoundString;
314ShapeBaseChartSet gShapeBasemap;
315extern bool g_CanvasHideNotificationIcon;
316
317// TODO why are these static?
318
328static int mouse_x;
338static int mouse_y;
339static bool mouse_leftisdown;
340
341bool g_brouteCreating;
342
343bool g_bShowTrackPointTime;
344
345int r_gamma_mult;
346int g_gamma_mult;
347int b_gamma_mult;
348int gamma_state;
349bool g_brightness_init;
350int last_brightness;
351
352int g_cog_predictor_width;
353extern double g_display_size_mm;
354
355extern ocpnFloatingToolbarDialog *g_MainToolbar;
356extern iENCToolbar *g_iENCToolbar;
357extern wxColour g_colourOwnshipRangeRingsColour;
358
359// LIVE ETA OPTION
360bool g_bShowLiveETA;
361extern double g_defaultBoatSpeed;
362double g_defaultBoatSpeedUserUnit;
363
364extern int g_nAIS_activity_timer;
365extern bool g_bskew_comp;
366extern float g_compass_scalefactor;
367extern int g_COGAvgSec; // COG average period (sec.) for Course Up Mode
368extern bool g_btenhertz;
369
370wxGLContext *g_pGLcontext; // shared common context
371
372extern bool g_useMUI;
373extern unsigned int g_canvasConfig;
374
375extern ChartCanvas *g_focusCanvas;
376extern ChartCanvas *g_overlayCanvas;
377
378extern float g_toolbar_scalefactor;
379extern SENCThreadManager *g_SencThreadManager;
380
381wxString g_ObjQFileExt;
382
383// "Curtain" mode parameters
384wxDialog *g_pcurtain;
385
386extern int g_GUIScaleFactor;
387// Win DPI scale factor
388double g_scaler;
389wxString g_lastS52PLIBPluginMessage;
390extern bool g_bChartBarEx;
391bool g_PrintingInProgress;
392
393#define MIN_BRIGHT 10
394#define MAX_BRIGHT 100
395
396//------------------------------------------------------------------------------
397// ChartCanvas Implementation
398//------------------------------------------------------------------------------
399BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
400EVT_PAINT(ChartCanvas::OnPaint)
401EVT_ACTIVATE(ChartCanvas::OnActivate)
402EVT_SIZE(ChartCanvas::OnSize)
403#ifndef HAVE_WX_GESTURE_EVENTS
404EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
405#endif
406EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
407EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
408EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
409EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
410EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
411EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
412EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
413EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
414EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
415EVT_KEY_UP(ChartCanvas::OnKeyUp)
416EVT_CHAR(ChartCanvas::OnKeyChar)
417EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
418EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
419EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
420EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
421EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
422EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
423EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
424EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
425
426END_EVENT_TABLE()
427
428// Define a constructor for my canvas
429ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
430 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
431 m_nmea_log(nmea_log) {
432 parent_frame = (MyFrame *)frame; // save a pointer to parent
433 m_canvasIndex = canvasIndex;
434
435 pscratch_bm = NULL;
436
437 SetBackgroundColour(wxColour(0, 0, 0));
438 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
439 // color scheme change
440
441 m_groupIndex = 0;
442 m_bDrawingRoute = false;
443 m_bRouteEditing = false;
444 m_bMarkEditing = false;
445 m_bRoutePoinDragging = false;
446 m_bIsInRadius = false;
447 m_bMayToggleMenuBar = true;
448
449 m_bFollow = false;
450 m_bShowNavobjects = true;
451 m_bTCupdate = false;
452 m_bAppendingRoute = false; // was true in MSW, why??
453 pThumbDIBShow = NULL;
454 m_bShowCurrent = false;
455 m_bShowTide = false;
456 bShowingCurrent = false;
457 pCwin = NULL;
458 warp_flag = false;
459 m_bzooming = false;
460 m_b_paint_enable = true;
461 m_routeState = 0;
462
463 pss_overlay_bmp = NULL;
464 pss_overlay_mask = NULL;
465 m_bChartDragging = false;
466 m_bMeasure_Active = false;
467 m_bMeasure_DistCircle = false;
468 m_pMeasureRoute = NULL;
469 m_pTrackRolloverWin = NULL;
470 m_pRouteRolloverWin = NULL;
471 m_pAISRolloverWin = NULL;
472 m_bedge_pan = false;
473 m_disable_edge_pan = false;
474 m_dragoffsetSet = false;
475 m_bautofind = false;
476 m_bFirstAuto = true;
477 m_groupIndex = 0;
478 m_singleChart = NULL;
479 m_upMode = NORTH_UP_MODE;
480 m_bShowAIS = true;
481 m_bShowAISScaled = false;
482 m_timed_move_vp_active = false;
483
484 m_vLat = 0.;
485 m_vLon = 0.;
486
487 m_pCIWin = NULL;
488
489 m_pSelectedRoute = NULL;
490 m_pSelectedTrack = NULL;
491 m_pRoutePointEditTarget = NULL;
492 m_pFoundPoint = NULL;
493 m_pMouseRoute = NULL;
494 m_prev_pMousePoint = NULL;
495 m_pEditRouteArray = NULL;
496 m_pFoundRoutePoint = NULL;
497 m_FinishRouteOnKillFocus = true;
498
499 m_pRolloverRouteSeg = NULL;
500 m_pRolloverTrackSeg = NULL;
501 m_bsectors_shown = false;
502
503 m_bbrightdir = false;
504 r_gamma_mult = 1;
505 g_gamma_mult = 1;
506 b_gamma_mult = 1;
507
508 m_pos_image_user_day = NULL;
509 m_pos_image_user_dusk = NULL;
510 m_pos_image_user_night = NULL;
511 m_pos_image_user_grey_day = NULL;
512 m_pos_image_user_grey_dusk = NULL;
513 m_pos_image_user_grey_night = NULL;
514
515 m_zoom_factor = 1;
516 m_rotation_speed = 0;
517 m_mustmove = 0;
518
519 m_OSoffsetx = 0.;
520 m_OSoffsety = 0.;
521
522 m_pos_image_user_yellow_day = NULL;
523 m_pos_image_user_yellow_dusk = NULL;
524 m_pos_image_user_yellow_night = NULL;
525
526 SetOwnShipState(SHIP_INVALID);
527
528 undo = new Undo(this);
529
530 VPoint.Invalidate();
531
532 m_glcc = NULL;
533
534 m_focus_indicator_pix = 1;
535
536 m_pCurrentStack = NULL;
537 m_bpersistent_quilt = false;
538 m_piano_ctx_menu = NULL;
539 m_Compass = NULL;
540 m_NotificationsList = NULL;
541 m_notification_button = NULL;
542
543 g_ChartNotRenderScaleFactor = 2.0;
544 m_bShowScaleInStatusBar = true;
545
546 m_muiBar = NULL;
547 m_bShowScaleInStatusBar = false;
548 m_show_focus_bar = true;
549
550 m_bShowOutlines = false;
551 m_bDisplayGrid = false;
552 m_bShowDepthUnits = true;
553 m_encDisplayCategory = (int)STANDARD;
554
555 m_encShowLights = true;
556 m_encShowAnchor = true;
557 m_encShowDataQual = false;
558 m_bShowGPS = true;
559 m_pQuilt = new Quilt(this);
560 SetQuiltMode(true);
561 SetAlertString(_T(""));
562 m_sector_glat = 0;
563 m_sector_glon = 0;
564 g_PrintingInProgress = false;
565
566#ifdef HAVE_WX_GESTURE_EVENTS
567 m_oldVPSScale = -1.0;
568 m_popupWanted = false;
569 m_leftdown = false;
570#endif /* HAVE_WX_GESTURE_EVENTS */
571
572 SetupGlCanvas();
573
574 singleClickEventIsValid = false;
575
576 // Build the cursors
577
578 pCursorLeft = NULL;
579 pCursorRight = NULL;
580 pCursorUp = NULL;
581 pCursorDown = NULL;
582 pCursorArrow = NULL;
583 pCursorPencil = NULL;
584 pCursorCross = NULL;
585
586 RebuildCursors();
587
588 SetCursor(*pCursorArrow);
589
590 pPanTimer = new wxTimer(this, m_MouseDragging);
591 pPanTimer->Stop();
592
593 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
594 pMovementTimer->Stop();
595
596 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
597 pMovementStopTimer->Stop();
598
599 pRotDefTimer = new wxTimer(this, ROT_TIMER);
600 pRotDefTimer->Stop();
601
602 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
603 m_DoubleClickTimer->Stop();
604
605 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
606 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
607 m_chart_drag_inertia_active = false;
608
609 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
610 m_animationActive = false;
611
612 m_panx = m_pany = 0;
613 m_panspeed = 0;
614 m_panx_target_final = m_pany_target_final = 0;
615 m_panx_target_now = m_pany_target_now = 0;
616
617 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
618 pCurTrackTimer->Stop();
619 m_curtrack_timer_msec = 10;
620
621 m_wheelzoom_stop_oneshot = 0;
622 m_last_wheel_dir = 0;
623
624 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
625
626 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
627
628 m_rollover_popup_timer_msec = 20;
629
630 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
631
632 m_b_rot_hidef = true;
633
634 proute_bm = NULL;
635 m_prot_bm = NULL;
636
637 m_upMode = NORTH_UP_MODE;
638 m_bLookAhead = false;
639
640 // Set some benign initial values
641
642 m_cs = GLOBAL_COLOR_SCHEME_DAY;
643 VPoint.clat = 0;
644 VPoint.clon = 0;
645 VPoint.view_scale_ppm = 1;
646 VPoint.Invalidate();
647 m_nMeasureState = 0;
648
649 m_canvas_scale_factor = 1.;
650
651 m_canvas_width = 1000;
652
653 m_overzoomTextWidth = 0;
654 m_overzoomTextHeight = 0;
655
656 // Create the default world chart
657 pWorldBackgroundChart = new GSHHSChart;
658 gShapeBasemap.Reset();
659
660 // Create the default depth unit emboss maps
661 m_pEM_Feet = NULL;
662 m_pEM_Meters = NULL;
663 m_pEM_Fathoms = NULL;
664
665 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
666
667 m_pEM_OverZoom = NULL;
668 SetOverzoomFont();
669 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
670
671 // Build icons for tide/current points
672 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
673 m_bmTideDay = style->GetIconScaled(_T("tidesml"),
674 1. / g_Platform->GetDisplayDIPMult(this));
675
676 // Dusk
677 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
678
679 // Night
680 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
681
682 // Build Dusk/Night ownship icons
683 double factor_dusk = 0.5;
684 double factor_night = 0.25;
685
686 // Red
687 m_os_image_red_day = style->GetIcon(_T("ship-red")).ConvertToImage();
688
689 int rimg_width = m_os_image_red_day.GetWidth();
690 int rimg_height = m_os_image_red_day.GetHeight();
691
692 m_os_image_red_dusk = m_os_image_red_day.Copy();
693 m_os_image_red_night = m_os_image_red_day.Copy();
694
695 for (int iy = 0; iy < rimg_height; iy++) {
696 for (int ix = 0; ix < rimg_width; ix++) {
697 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
698 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
699 m_os_image_red_day.GetGreen(ix, iy),
700 m_os_image_red_day.GetBlue(ix, iy));
701 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
702 hsv.value = hsv.value * factor_dusk;
703 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
704 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
705
706 hsv = wxImage::RGBtoHSV(rgb);
707 hsv.value = hsv.value * factor_night;
708 nrgb = wxImage::HSVtoRGB(hsv);
709 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
710 }
711 }
712 }
713
714 // Grey
715 m_os_image_grey_day =
716 style->GetIcon(_T("ship-red")).ConvertToImage().ConvertToGreyscale();
717
718 int gimg_width = m_os_image_grey_day.GetWidth();
719 int gimg_height = m_os_image_grey_day.GetHeight();
720
721 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
722 m_os_image_grey_night = m_os_image_grey_day.Copy();
723
724 for (int iy = 0; iy < gimg_height; iy++) {
725 for (int ix = 0; ix < gimg_width; ix++) {
726 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
727 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
728 m_os_image_grey_day.GetGreen(ix, iy),
729 m_os_image_grey_day.GetBlue(ix, iy));
730 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
731 hsv.value = hsv.value * factor_dusk;
732 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
733 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
734
735 hsv = wxImage::RGBtoHSV(rgb);
736 hsv.value = hsv.value * factor_night;
737 nrgb = wxImage::HSVtoRGB(hsv);
738 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
739 }
740 }
741 }
742
743 // Yellow
744 m_os_image_yellow_day = m_os_image_red_day.Copy();
745
746 gimg_width = m_os_image_yellow_day.GetWidth();
747 gimg_height = m_os_image_yellow_day.GetHeight();
748
749 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
750 m_os_image_yellow_night = m_os_image_red_day.Copy();
751
752 for (int iy = 0; iy < gimg_height; iy++) {
753 for (int ix = 0; ix < gimg_width; ix++) {
754 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
755 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
756 m_os_image_yellow_day.GetGreen(ix, iy),
757 m_os_image_yellow_day.GetBlue(ix, iy));
758 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
759 hsv.hue += 60. / 360.; // shift to yellow
760 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
761 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
762
763 hsv = wxImage::RGBtoHSV(rgb);
764 hsv.value = hsv.value * factor_dusk;
765 hsv.hue += 60. / 360.; // shift to yellow
766 nrgb = wxImage::HSVtoRGB(hsv);
767 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
768
769 hsv = wxImage::RGBtoHSV(rgb);
770 hsv.hue += 60. / 360.; // shift to yellow
771 hsv.value = hsv.value * factor_night;
772 nrgb = wxImage::HSVtoRGB(hsv);
773 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
774 }
775 }
776 }
777
778 // Set initial pointers to ownship images
779 m_pos_image_red = &m_os_image_red_day;
780 m_pos_image_yellow = &m_os_image_yellow_day;
781 m_pos_image_grey = &m_os_image_grey_day;
782
783 SetUserOwnship();
784
785 m_pBrightPopup = NULL;
786
787#ifdef ocpnUSE_GL
788 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
789#endif
790
791 int gridFontSize = 8;
792#if defined(__WXOSX__) || defined(__WXGTK3__)
793 // Support scaled HDPI displays.
794 gridFontSize *= GetContentScaleFactor();
795#endif
796
797 m_pgridFont = FontMgr::Get().FindOrCreateFont(
798 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
799 FALSE, wxString(_T ( "Arial" )));
800
801 m_Piano = new Piano(this);
802
803 m_bShowCompassWin = true;
804
805 m_Compass = new ocpnCompass(this);
806 m_Compass->SetScaleFactor(g_compass_scalefactor);
807 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
808
809 m_notification_button = new NotificationButton(this);
810 m_notification_button->SetScaleFactor(g_compass_scalefactor);
811 m_notification_button->Show(true);
812
813 m_pianoFrozen = false;
814
815 SetMinSize(wxSize(200, 200));
816
817 m_displayScale = 1.0;
818#if defined(__WXOSX__) || defined(__WXGTK3__)
819 // Support scaled HDPI displays.
820 m_displayScale = GetContentScaleFactor();
821#endif
822 VPoint.SetPixelScale(m_displayScale);
823
824#ifdef HAVE_WX_GESTURE_EVENTS
825 // if (!m_glcc)
826 {
827 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
828 wxLogError("Failed to enable touch events");
829 }
830
831 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
832
833 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
834 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
835
836 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
837 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
838
839 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
840 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
841
842 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
843 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
844 }
845#endif
846
847 // Listen for notification events
848 auto &noteman = NotificationManager::GetInstance();
849
850 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
851 evt_notificationlist_change_listener.Listen(
852 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
853 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
854 if (m_NotificationsList && m_NotificationsList->IsShown()) {
855 m_NotificationsList->ReloadNotificationList();
856 }
857 Refresh();
858 });
859}
860
861ChartCanvas::~ChartCanvas() {
862 delete pThumbDIBShow;
863
864 // Delete Cursors
865 delete pCursorLeft;
866 delete pCursorRight;
867 delete pCursorUp;
868 delete pCursorDown;
869 delete pCursorArrow;
870 delete pCursorPencil;
871 delete pCursorCross;
872
873 delete pPanTimer;
874 delete pMovementTimer;
875 delete pMovementStopTimer;
876 delete pCurTrackTimer;
877 delete pRotDefTimer;
878 delete m_DoubleClickTimer;
879
880 delete m_pTrackRolloverWin;
881 delete m_pRouteRolloverWin;
882 delete m_pAISRolloverWin;
883 delete m_pBrightPopup;
884
885 delete m_pCIWin;
886
887 delete pscratch_bm;
888
889 m_dc_route.SelectObject(wxNullBitmap);
890 delete proute_bm;
891
892 delete pWorldBackgroundChart;
893 delete pss_overlay_bmp;
894
895 delete m_pEM_Feet;
896 delete m_pEM_Meters;
897 delete m_pEM_Fathoms;
898
899 delete m_pEM_OverZoom;
900 // delete m_pEM_CM93Offset;
901
902 delete m_prot_bm;
903
904 delete m_pos_image_user_day;
905 delete m_pos_image_user_dusk;
906 delete m_pos_image_user_night;
907 delete m_pos_image_user_grey_day;
908 delete m_pos_image_user_grey_dusk;
909 delete m_pos_image_user_grey_night;
910 delete m_pos_image_user_yellow_day;
911 delete m_pos_image_user_yellow_dusk;
912 delete m_pos_image_user_yellow_night;
913
914 delete undo;
915#ifdef ocpnUSE_GL
916 if (!g_bdisable_opengl) {
917 delete m_glcc;
918
919#if wxCHECK_VERSION(2, 9, 0)
920 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
921#endif
922 }
923#endif
924
925 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
926 // wx tries to deliver events to this canvas during destroy.
927 MUIBar *muiBar = m_muiBar;
928 m_muiBar = 0;
929 delete muiBar;
930 delete m_pQuilt;
931 delete m_pCurrentStack;
932 delete m_Compass;
933 delete m_Piano;
934}
935
936void ChartCanvas::RebuildCursors() {
937 delete pCursorLeft;
938 delete pCursorRight;
939 delete pCursorUp;
940 delete pCursorDown;
941 delete pCursorArrow;
942 delete pCursorPencil;
943 delete pCursorCross;
944
945 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
946 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
947
948 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
949
950 wxImage ICursorLeft = style->GetIcon(_T("left")).ConvertToImage();
951 wxImage ICursorRight = style->GetIcon(_T("right")).ConvertToImage();
952 wxImage ICursorUp = style->GetIcon(_T("up")).ConvertToImage();
953 wxImage ICursorDown = style->GetIcon(_T("down")).ConvertToImage();
954 wxImage ICursorPencil =
955 style->GetIconScaled(_T("pencil"), pencilScale).ConvertToImage();
956 wxImage ICursorCross = style->GetIcon(_T("cross")).ConvertToImage();
957
958#if !defined(__WXMSW__) && !defined(__WXQT__)
959 ICursorLeft.ConvertAlphaToMask(128);
960 ICursorRight.ConvertAlphaToMask(128);
961 ICursorUp.ConvertAlphaToMask(128);
962 ICursorDown.ConvertAlphaToMask(128);
963 ICursorPencil.ConvertAlphaToMask(10);
964 ICursorCross.ConvertAlphaToMask(10);
965#endif
966
967 if (ICursorLeft.Ok()) {
968 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
969 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
970 pCursorLeft = new wxCursor(ICursorLeft);
971 } else
972 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
973
974 if (ICursorRight.Ok()) {
975 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
976 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
977 pCursorRight = new wxCursor(ICursorRight);
978 } else
979 pCursorRight = new wxCursor(wxCURSOR_ARROW);
980
981 if (ICursorUp.Ok()) {
982 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
983 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
984 pCursorUp = new wxCursor(ICursorUp);
985 } else
986 pCursorUp = new wxCursor(wxCURSOR_ARROW);
987
988 if (ICursorDown.Ok()) {
989 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
990 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
991 pCursorDown = new wxCursor(ICursorDown);
992 } else
993 pCursorDown = new wxCursor(wxCURSOR_ARROW);
994
995 if (ICursorPencil.Ok()) {
996 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
997 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
998 pCursorPencil = new wxCursor(ICursorPencil);
999 } else
1000 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
1001
1002 if (ICursorCross.Ok()) {
1003 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
1004 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
1005 pCursorCross = new wxCursor(ICursorCross);
1006 } else
1007 pCursorCross = new wxCursor(wxCURSOR_ARROW);
1008
1009 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
1010 pPlugIn_Cursor = NULL;
1011}
1012
1013void ChartCanvas::CanvasApplyLocale() {
1014 CreateDepthUnitEmbossMaps(m_cs);
1015 CreateOZEmbossMapData(m_cs);
1016}
1017
1018void ChartCanvas::SetupGlCanvas() {
1019#ifndef __ANDROID__
1020#ifdef ocpnUSE_GL
1021 if (!g_bdisable_opengl) {
1022 if (g_bopengl) {
1023 wxLogMessage(_T("Creating glChartCanvas"));
1024 m_glcc = new glChartCanvas(this);
1025
1026 // We use one context for all GL windows, so that textures etc will be
1027 // automatically shared
1028 if (IsPrimaryCanvas()) {
1029 // qDebug() << "Creating Primary Context";
1030
1031 // wxGLContextAttrs ctxAttr;
1032 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
1033 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
1034 // NULL, &ctxAttr);
1035 wxGLContext *pctx = new wxGLContext(m_glcc);
1036 m_glcc->SetContext(pctx);
1037 g_pGLcontext = pctx; // Save a copy of the common context
1038 } else {
1039#ifdef __WXOSX__
1040 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
1041#else
1042 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
1043 // saved common context
1044#endif
1045 }
1046 }
1047 }
1048#endif
1049#endif
1050
1051#ifdef __ANDROID__ // ocpnUSE_GL
1052 if (!g_bdisable_opengl) {
1053 if (g_bopengl) {
1054 // qDebug() << "SetupGlCanvas";
1055 wxLogMessage(_T("Creating glChartCanvas"));
1056
1057 // We use one context for all GL windows, so that textures etc will be
1058 // automatically shared
1059 if (IsPrimaryCanvas()) {
1060 qDebug() << "Creating Primary glChartCanvas";
1061
1062 // wxGLContextAttrs ctxAttr;
1063 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
1064 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
1065 // NULL, &ctxAttr);
1066 m_glcc = new glChartCanvas(this);
1067
1068 wxGLContext *pctx = new wxGLContext(m_glcc);
1069 m_glcc->SetContext(pctx);
1070 g_pGLcontext = pctx; // Save a copy of the common context
1071 m_glcc->m_pParentCanvas = this;
1072 // m_glcc->Reparent(this);
1073 } else {
1074 qDebug() << "Creating Secondary glChartCanvas";
1075 // QGLContext *pctx =
1076 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
1077 // << "pctx: " << pctx;
1078
1079 m_glcc = new glChartCanvas(
1080 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
1081 // m_glcc = new glChartCanvas(this, pctx); //Shared
1082 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
1083 wxGLContext *pwxctx = new wxGLContext(m_glcc);
1084 m_glcc->SetContext(pwxctx);
1085 m_glcc->m_pParentCanvas = this;
1086 // m_glcc->Reparent(this);
1087 }
1088 }
1089 }
1090#endif
1091}
1092
1093void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
1094 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1095
1096 // On Android, we get a KillFocus on just about every keystroke.
1097 // Why?
1098#ifdef __ANDROID__
1099 return;
1100#endif
1101
1102 // Special logic:
1103 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
1104 // canvas focus. Why??? Who knows... So, we provide for this case by
1105 // starting a timer if required to actually Finish() a route on a legitimate
1106 // focus change, but not if the focus is quickly regained ( <20 msec.) on
1107 // this canvas.
1108#ifdef __WXOSX__
1109 if (m_routeState && m_FinishRouteOnKillFocus)
1110 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
1111#else
1112 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1113#endif
1114}
1115
1116void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
1117 m_routeFinishTimer.Stop();
1118
1119 // Try to keep the global top-line menubar selections up to date with the
1120 // current "focus" canvas
1121 gFrame->UpdateGlobalMenuItems(this);
1122
1123 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1124}
1125
1126void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
1127 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1128}
1129
1130#ifdef HAVE_WX_GESTURE_EVENTS
1131void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
1132 /* we defer the popup menu call upon the leftup event
1133 else the menu disappears immediately,
1134 (see
1135 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
1136 */
1137 m_popupWanted = true;
1138}
1139
1140void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
1141 // not implemented yet
1142}
1143
1144void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1145
1146void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1147
1148void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1149 wxPoint pos = event.GetPosition();
1150
1151 m_leftdown = false;
1152
1153 if (!m_popupWanted) {
1154 wxMouseEvent ev(wxEVT_LEFT_UP);
1155 ev.m_x = pos.x;
1156 ev.m_y = pos.y;
1157 MouseEvent(ev);
1158 return;
1159 }
1160
1161 m_popupWanted = false;
1162
1163 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1164 ev.m_x = pos.x;
1165 ev.m_y = pos.y;
1166
1167 MouseEvent(ev);
1168}
1169
1170void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1171 m_leftdown = true;
1172
1173 wxPoint pos = event.GetPosition();
1174 MouseEvent(event);
1175}
1176
1177void ChartCanvas::OnMotion(wxMouseEvent &event) {
1178 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1179 dragging, upon simple click, and without the OnLeftDown event before Thus,
1180 this consists in skiping it, and setting the leftdown bit according to a
1181 status that we trust */
1182 event.m_leftDown = m_leftdown;
1183 MouseEvent(event);
1184}
1185
1186void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1187 /* there are spurious end zoom events upon right-click */
1188 if (event.IsGestureEnd()) return;
1189
1190 double factor = event.GetZoomFactor();
1191
1192 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1193 m_oldVPSScale = GetVPScale();
1194 }
1195
1196 double current_vps = GetVPScale();
1197 double wanted_factor = m_oldVPSScale / current_vps * factor;
1198
1199 ZoomCanvas(wanted_factor, true, false);
1200
1201 // Allow combined zoom/pan operation
1202 if (event.IsGestureStart()) {
1203 m_zoomStartPoint = event.GetPosition();
1204 } else {
1205 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1206 PanCanvas(-delta.x, -delta.y);
1207 m_zoomStartPoint = event.GetPosition();
1208 }
1209}
1210
1211void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1212
1213void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1214 DoRotateCanvas(0.0);
1215}
1216#endif /* HAVE_WX_GESTURE_EVENTS */
1217
1218void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1219 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1220 m_vLat = pcc->iLat;
1221 m_vLon = pcc->iLon;
1222
1223 m_restore_dbindex = pcc->DBindex;
1224 m_bFollow = pcc->bFollow;
1225 if (pcc->GroupID < 0) pcc->GroupID = 0;
1226
1227 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1228 m_groupIndex = 0;
1229 else
1230 m_groupIndex = pcc->GroupID;
1231
1232 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1233
1234 ShowTides(pcc->bShowTides);
1235 ShowCurrents(pcc->bShowCurrents);
1236
1237 SetShowDepthUnits(pcc->bShowDepthUnits);
1238 SetShowGrid(pcc->bShowGrid);
1239 SetShowOutlines(pcc->bShowOutlines);
1240
1241 SetShowAIS(pcc->bShowAIS);
1242 SetAttenAIS(pcc->bAttenAIS);
1243
1244 // ENC options
1245 SetShowENCText(pcc->bShowENCText);
1246 m_encDisplayCategory = pcc->nENCDisplayCategory;
1247 m_encShowDepth = pcc->bShowENCDepths;
1248 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1249 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1250 m_encShowLights = pcc->bShowENCLights;
1251 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1252 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1253 m_encShowDataQual = pcc->bShowENCDataQuality;
1254
1255 bool courseUp = pcc->bCourseUp;
1256 bool headUp = pcc->bHeadUp;
1257 m_upMode = NORTH_UP_MODE;
1258 if (courseUp)
1259 m_upMode = COURSE_UP_MODE;
1260 else if (headUp)
1261 m_upMode = HEAD_UP_MODE;
1262
1263 m_bLookAhead = pcc->bLookahead;
1264
1265 m_singleChart = NULL;
1266}
1267
1268void ChartCanvas::ApplyGlobalSettings() {
1269 // GPS compas window
1270 if (m_Compass) {
1271 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1272 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1273 }
1274 m_notification_button->UpdateStatus();
1275}
1276
1277void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1278 bool groupOK = CheckGroup(m_groupIndex);
1279
1280 if (!groupOK) {
1281 SetGroupIndex(m_groupIndex, true);
1282 }
1283}
1284
1285void ChartCanvas::SetShowGPS(bool bshow) {
1286 if (m_bShowGPS != bshow) {
1287 delete m_Compass;
1288 m_Compass = new ocpnCompass(this, bshow);
1289 m_Compass->SetScaleFactor(g_compass_scalefactor);
1290 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1291 }
1292 m_bShowGPS = bshow;
1293}
1294
1295void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1296 m_bShowCompassWin = bshow;
1297 if (m_Compass) {
1298 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1299 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1300 }
1301}
1302
1303int ChartCanvas::GetPianoHeight() {
1304 int height = 0;
1305 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1306
1307 return height;
1308}
1309
1310void ChartCanvas::ConfigureChartBar() {
1311 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1312
1313 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
1314 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
1315
1316 if (GetQuiltMode()) {
1317 m_Piano->SetRoundedRectangles(true);
1318 }
1319 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
1320 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon(_T("polyprj"))));
1321 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
1322}
1323
1324void ChartCanvas::ShowTides(bool bShow) {
1325 gFrame->LoadHarmonics();
1326
1327 if (ptcmgr->IsReady()) {
1328 SetbShowTide(bShow);
1329
1330 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1331 } else {
1332 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1333 SetbShowTide(false);
1334 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1335 }
1336
1337 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1338 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1339
1340 // TODO
1341 // if( GetbShowTide() ) {
1342 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1343 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1344 // update
1345 // } else
1346 // FrameTCTimer.Stop();
1347}
1348
1349void ChartCanvas::ShowCurrents(bool bShow) {
1350 gFrame->LoadHarmonics();
1351
1352 if (ptcmgr->IsReady()) {
1353 SetbShowCurrent(bShow);
1354 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1355 } else {
1356 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1357 SetbShowCurrent(false);
1358 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1359 }
1360
1361 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1362 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1363
1364 // TODO
1365 // if( GetbShowCurrent() ) {
1366 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1367 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1368 // update
1369 // } else
1370 // FrameTCTimer.Stop();
1371}
1372
1373// TODO
1374extern bool g_bPreserveScaleOnX;
1375extern ChartDummy *pDummyChart;
1376extern int g_sticky_chart;
1377
1378void ChartCanvas::canvasRefreshGroupIndex(void) { SetGroupIndex(m_groupIndex); }
1379
1380void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1381 SetAlertString(_T(""));
1382
1383 int new_index = index;
1384 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1385
1386 bool bgroup_override = false;
1387 int old_group_index = new_index;
1388
1389 if (!CheckGroup(new_index)) {
1390 new_index = 0;
1391 bgroup_override = true;
1392 }
1393
1394 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1395 new_index = index;
1396
1397 // Get the currently displayed chart native scale, and the current ViewPort
1398 int current_chart_native_scale = GetCanvasChartNativeScale();
1399 ViewPort vp = GetVP();
1400
1401 m_groupIndex = new_index;
1402
1403 // Are there ENCs in this group
1404 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1405
1406 // Update the MUIBar for ENC availability
1407 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1408
1409 // Allow the chart database to pre-calculate the MBTile inclusion test
1410 // boolean...
1411 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1412
1413 // Invalidate the "sticky" chart on group change, since it might not be in
1414 // the new group
1415 g_sticky_chart = -1;
1416
1417 // We need a chartstack and quilt to figure out which chart to open in the
1418 // new group
1419 UpdateCanvasOnGroupChange();
1420
1421 int dbi_now = -1;
1422 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1423
1424 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1425
1426 // If a new reference chart is indicated, set a good scale for it.
1427 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1428 double best_scale = GetBestStartScale(dbi_hint, vp);
1429 SetVPScale(best_scale);
1430 }
1431
1432 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1433
1434 // Refresh the canvas, selecting the "best" chart,
1435 // applying the prior ViewPort exactly
1436 canvasChartsRefresh(dbi_hint);
1437
1438 UpdateCanvasControlBar();
1439
1440 if (!autoSwitch && bgroup_override) {
1441 // show a short timed message box
1442 wxString msg(_("Group \""));
1443
1444 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1445 msg += pGroup->m_group_name;
1446
1447 msg += _("\" is empty.");
1448
1449 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1450
1451 return;
1452 }
1453
1454 // Message box is deferred so that canvas refresh occurs properly before
1455 // dialog
1456 if (bgroup_override) {
1457 wxString msg(_("Group \""));
1458
1459 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1460 msg += pGroup->m_group_name;
1461
1462 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1463
1464 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1465 }
1466}
1467
1468bool ChartCanvas::CheckGroup(int igroup) {
1469 if (!ChartData) return true; // Not known yet...
1470
1471 if (igroup == 0) return true; // "all charts" is always OK
1472
1473 if (igroup < 0) // negative group is an error
1474 return false;
1475
1476 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1477
1478 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1479 // and auto-shift to group 0
1480 return false;
1481
1482 for (const auto &elem : pGroup->m_element_array) {
1483 for (unsigned int ic = 0;
1484 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1485 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1486 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1487
1488 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1489 }
1490 }
1491
1492 // If necessary, check for GSHHS
1493 for (const auto &elem : pGroup->m_element_array) {
1494 const wxString &element_root = elem.m_element_name;
1495 wxString test_string = _T("GSHH");
1496 if (element_root.Upper().Contains(test_string)) return true;
1497 }
1498
1499 return false;
1500}
1501
1502void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1503 if (!ChartData) return;
1504
1505 AbstractPlatform::ShowBusySpinner();
1506
1507 double old_scale = GetVPScale();
1508 InvalidateQuilt();
1509 SetQuiltRefChart(-1);
1510
1511 m_singleChart = NULL;
1512
1513 // delete m_pCurrentStack;
1514 // m_pCurrentStack = NULL;
1515
1516 // Build a new ChartStack
1517 if (!m_pCurrentStack) {
1518 m_pCurrentStack = new ChartStack;
1519 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1520 }
1521
1522 if (-1 != dbi_hint) {
1523 if (GetQuiltMode()) {
1524 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1525 SetQuiltRefChart(dbi_hint);
1526 } else {
1527 // Open the saved chart
1528 ChartBase *pTentative_Chart;
1529 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1530
1531 if (pTentative_Chart) {
1532 /* m_singleChart is always NULL here, (set above) should this go before
1533 * that? */
1534 if (m_singleChart) m_singleChart->Deactivate();
1535
1536 m_singleChart = pTentative_Chart;
1537 m_singleChart->Activate();
1538
1539 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1540 GetpCurrentStack(), m_singleChart->GetFullPath());
1541 }
1542 }
1543
1544 // refresh_Piano();
1545 } else {
1546 // Select reference chart from the stack, as though clicked by user
1547 // Make it the smallest scale chart on the stack
1548 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1549 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1550 SetQuiltRefChart(selected_index);
1551 }
1552
1553 // Validate the correct single chart, or set the quilt mode as appropriate
1554 SetupCanvasQuiltMode();
1555 if (!GetQuiltMode() && m_singleChart == 0) {
1556 // use a dummy like in DoChartUpdate
1557 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1558 m_singleChart = pDummyChart;
1559 SetVPScale(old_scale);
1560 }
1561
1562 ReloadVP();
1563
1564 UpdateCanvasControlBar();
1565 UpdateGPSCompassStatusBox(true);
1566
1567 SetCursor(wxCURSOR_ARROW);
1568
1569 AbstractPlatform::HideBusySpinner();
1570}
1571
1572bool ChartCanvas::DoCanvasUpdate(void) {
1573 double tLat, tLon; // Chart Stack location
1574 double vpLat, vpLon; // ViewPort location
1575 bool blong_jump = false;
1576 meters_to_shift = 0;
1577 dir_to_shift = 0;
1578
1579 bool bNewChart = false;
1580 bool bNewView = false;
1581 bool bCanvasChartAutoOpen = true; // debugging
1582
1583 bool bNewPiano = false;
1584 bool bOpenSpecified;
1585 ChartStack LastStack;
1586 ChartBase *pLast_Ch;
1587
1588 ChartStack WorkStack;
1589
1590 if (bDBUpdateInProgress) return false;
1591 if (!ChartData) return false;
1592
1593 if (ChartData->IsBusy()) return false;
1594
1595 // Startup case:
1596 // Quilting is enabled, but the last chart seen was not quiltable
1597 // In this case, drop to single chart mode, set persistence flag,
1598 // And open the specified chart
1599 // TODO implement this
1600 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1601 // if( GetQuiltMode() ) {
1602 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1603 // gFrame->ToggleQuiltMode();
1604 // m_bpersistent_quilt = true;
1605 // m_singleChart = NULL;
1606 // }
1607 // }
1608 // }
1609
1610 // If in auto-follow mode, use the current glat,glon to build chart
1611 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1612 // other means
1613
1614 if (m_bFollow) {
1615 tLat = gLat;
1616 tLon = gLon;
1617
1618 // Set the ViewPort center based on the OWNSHIP offset
1619 double dx = m_OSoffsetx;
1620 double dy = m_OSoffsety;
1621 double d_east = dx / GetVP().view_scale_ppm;
1622 double d_north = dy / GetVP().view_scale_ppm;
1623
1624 if (GetUpMode() == NORTH_UP_MODE) {
1625 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1626 } else {
1627 double offset_angle = atan2(d_north, d_east);
1628 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1629 double chart_angle = GetVPRotation();
1630 double target_angle = chart_angle + offset_angle;
1631 double d_east_mod = offset_distance * cos(target_angle);
1632 double d_north_mod = offset_distance * sin(target_angle);
1633 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1634 }
1635
1636 extern double gCog_gt;
1637
1638 // on lookahead mode, adjust the vp center point
1639 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1640 double cog_to_use = gCog;
1641 if (g_btenhertz &&
1642 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1643 cog_to_use = gCog_gt;
1644 blong_jump = true;
1645 }
1646 if (!g_btenhertz) cog_to_use = g_COGAvg;
1647
1648 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1649
1650 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1651 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1652
1653 double pixel_delta_tent =
1654 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1655
1656 double pixel_delta = 0;
1657
1658 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1659 // avoid jumping of the vp center point during slow maneuvering, or at
1660 // anchor....
1661 if (!std::isnan(gSog)) {
1662 if (gSog < 2.0)
1663 pixel_delta = 0.;
1664 else
1665 pixel_delta = pixel_delta_tent;
1666 }
1667
1668 meters_to_shift = 0;
1669 dir_to_shift = 0;
1670 if (!std::isnan(gCog)) {
1671 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1672 dir_to_shift = cog_to_use;
1673 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1674 &vpLon);
1675 } else {
1676 vpLat = gLat;
1677 vpLon = gLon;
1678 }
1679 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1680 m_OSoffsetx = 0; // center ownship on loss of GPS
1681 m_OSoffsety = 0;
1682 vpLat = gLat;
1683 vpLon = gLon;
1684 }
1685
1686 } else {
1687 tLat = m_vLat;
1688 tLon = m_vLon;
1689 vpLat = m_vLat;
1690 vpLon = m_vLon;
1691 }
1692
1693 if (GetQuiltMode()) {
1694 int current_db_index = -1;
1695 if (m_pCurrentStack)
1696 current_db_index =
1697 m_pCurrentStack
1698 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1699 // chart dbIndex
1700 else
1701 m_pCurrentStack = new ChartStack;
1702
1703 // This logic added to enable opening a chart when there is no
1704 // previous chart indication, either from inital startup, or from adding
1705 // new chart directory
1706 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1707 m_pCurrentStack) {
1708 if (m_pCurrentStack->nEntry) {
1709 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1710 1); // smallest scale
1711 SelectQuiltRefdbChart(new_dbIndex, true);
1712 m_bautofind = false;
1713 }
1714 }
1715
1716 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1717 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1718
1719 if (m_bFirstAuto) {
1720 // Allow the chart database to pre-calculate the MBTile inclusion test
1721 // boolean...
1722 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1723
1724 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1725 // physical pixels. On standard DPI displays where logical = physical
1726 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1727 // logical pixels, this ratio would be 0.5.
1728 double proposed_scale_onscreen =
1729 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1730
1731 int initial_db_index = m_restore_dbindex;
1732 if (initial_db_index < 0) {
1733 if (m_pCurrentStack->nEntry) {
1734 initial_db_index =
1735 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1736 } else
1737 m_bautofind = true; // initial_db_index = 0;
1738 }
1739
1740 if (m_pCurrentStack->nEntry) {
1741 int initial_type = ChartData->GetDBChartType(initial_db_index);
1742
1743 // Check to see if the target new chart is quiltable as a reference
1744 // chart
1745
1746 if (!IsChartQuiltableRef(initial_db_index)) {
1747 // If it is not quiltable, then walk the stack up looking for a
1748 // satisfactory chart i.e. one that is quiltable and of the same type
1749 // XXX if there's none?
1750 int stack_index = 0;
1751
1752 if (stack_index >= 0) {
1753 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1754 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1755 if (IsChartQuiltableRef(test_db_index) &&
1756 (initial_type ==
1757 ChartData->GetDBChartType(initial_db_index))) {
1758 initial_db_index = test_db_index;
1759 break;
1760 }
1761 stack_index++;
1762 }
1763 }
1764 }
1765
1766 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1767 if (pc) {
1768 SetQuiltRefChart(initial_db_index);
1769 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1770 }
1771 }
1772 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1773 // just GetVPScale(), so I'm not sure why it's necessary to define the
1774 // proposed_scale_onscreen variable.
1775 bNewView |= SetViewPoint(vpLat, vpLon,
1776 GetCanvasScaleFactor() / proposed_scale_onscreen,
1777 0, GetVPRotation());
1778 }
1779 // Measure rough jump distance if in bfollow mode
1780 // No good reason to do smooth pan for
1781 // jump distance more than one screen width at scale.
1782 bool super_jump = false;
1783 if (m_bFollow) {
1784 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1785 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1786 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1787 }
1788 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead) {
1789 int nstep = 5;
1790 if (blong_jump) nstep = 20;
1791 StartTimedMovementVP(vpLat, vpLon, nstep);
1792 } else {
1793 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1794 }
1795
1796 goto update_finish;
1797 }
1798
1799 // Single Chart Mode from here....
1800 pLast_Ch = m_singleChart;
1801 ChartTypeEnum new_open_type;
1802 ChartFamilyEnum new_open_family;
1803 if (pLast_Ch) {
1804 new_open_type = pLast_Ch->GetChartType();
1805 new_open_family = pLast_Ch->GetChartFamily();
1806 } else {
1807 new_open_type = CHART_TYPE_KAP;
1808 new_open_family = CHART_FAMILY_RASTER;
1809 }
1810
1811 bOpenSpecified = m_bFirstAuto;
1812
1813 // Make sure the target stack is valid
1814 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1815
1816 // Build a chart stack based on tLat, tLon
1817 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1818 m_groupIndex)) { // Bogus Lat, Lon?
1819 if (NULL == pDummyChart) {
1820 pDummyChart = new ChartDummy;
1821 bNewChart = true;
1822 }
1823
1824 if (m_singleChart)
1825 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1826
1827 m_singleChart = pDummyChart;
1828
1829 // If the current viewpoint is invalid, set the default scale to
1830 // something reasonable.
1831 double set_scale = GetVPScale();
1832 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1833
1834 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1835
1836 // If the chart stack has just changed, there is new status
1837 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1838 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1839 bNewPiano = true;
1840 bNewChart = true;
1841 }
1842 }
1843
1844 // Copy the new (by definition empty) stack into the target stack
1845 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1846
1847 goto update_finish;
1848 }
1849
1850 // Check to see if Chart Stack has changed
1851 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1852 // New chart stack, so...
1853 bNewPiano = true;
1854
1855 // Save a copy of the current stack
1856 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1857
1858 // Copy the new stack into the target stack
1859 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1860
1861 // Is Current Chart in new stack?
1862
1863 int tEntry = -1;
1864 if (NULL != m_singleChart) // this handles startup case
1865 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1866 m_singleChart->GetFullPath());
1867
1868 if (tEntry != -1) { // m_singleChart is in the new stack
1869 m_pCurrentStack->CurrentStackEntry = tEntry;
1870 bNewChart = false;
1871 }
1872
1873 else // m_singleChart is NOT in new stack
1874 { // So, need to open a new chart
1875 // Find the largest scale raster chart that opens OK
1876
1877 ChartBase *pProposed = NULL;
1878
1879 if (bCanvasChartAutoOpen) {
1880 bool search_direction =
1881 false; // default is to search from lowest to highest
1882 int start_index = 0;
1883
1884 // A special case: If panning at high scale, open largest scale
1885 // chart first
1886 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1887 (LastStack.nEntry == 0)) {
1888 search_direction = true;
1889 start_index = m_pCurrentStack->nEntry - 1;
1890 }
1891
1892 // Another special case, open specified index on program start
1893 if (bOpenSpecified) {
1894 search_direction = false;
1895 start_index = 0;
1896 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1897 start_index = 0;
1898
1899 new_open_type = CHART_TYPE_DONTCARE;
1900 }
1901
1902 pProposed = ChartData->OpenStackChartConditional(
1903 m_pCurrentStack, start_index, search_direction, new_open_type,
1904 new_open_family);
1905
1906 // Try to open other types/families of chart in some priority
1907 if (NULL == pProposed)
1908 pProposed = ChartData->OpenStackChartConditional(
1909 m_pCurrentStack, start_index, search_direction,
1910 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1911
1912 if (NULL == pProposed)
1913 pProposed = ChartData->OpenStackChartConditional(
1914 m_pCurrentStack, start_index, search_direction,
1915 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1916
1917 bNewChart = true;
1918
1919 } // bCanvasChartAutoOpen
1920
1921 else
1922 pProposed = NULL;
1923
1924 // If no go, then
1925 // Open a Dummy Chart
1926 if (NULL == pProposed) {
1927 if (NULL == pDummyChart) {
1928 pDummyChart = new ChartDummy;
1929 bNewChart = true;
1930 }
1931
1932 if (pLast_Ch)
1933 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1934
1935 pProposed = pDummyChart;
1936 }
1937
1938 // Arriving here, pProposed points to an opened chart, or NULL.
1939 if (m_singleChart) m_singleChart->Deactivate();
1940 m_singleChart = pProposed;
1941
1942 if (m_singleChart) {
1943 m_singleChart->Activate();
1944 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1945 m_pCurrentStack, m_singleChart->GetFullPath());
1946 }
1947 } // need new chart
1948
1949 // Arriving here, m_singleChart is opened and OK, or NULL
1950 if (NULL != m_singleChart) {
1951 // Setup the view using the current scale
1952 double set_scale = GetVPScale();
1953
1954 // If the current viewpoint is invalid, set the default scale to
1955 // something reasonable.
1956 if (!GetVP().IsValid())
1957 set_scale = 1. / 20000.;
1958 else { // otherwise, match scale if elected.
1959 double proposed_scale_onscreen;
1960
1961 if (m_bFollow) { // autoset the scale only if in autofollow
1962 double new_scale_ppm =
1963 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1964 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1965 } else
1966 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1967
1968 // This logic will bring a new chart onscreen at roughly twice the true
1969 // paper scale equivalent. Note that first chart opened on application
1970 // startup (bOpenSpecified = true) will open at the config saved scale
1971 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1972 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1973 double equivalent_vp_scale =
1974 GetCanvasScaleFactor() / proposed_scale_onscreen;
1975 double new_scale_ppm =
1976 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1977 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1978 }
1979
1980 if (m_bFollow) { // bounds-check the scale only if in autofollow
1981 proposed_scale_onscreen =
1982 wxMin(proposed_scale_onscreen,
1983 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1984 GetCanvasWidth()));
1985 proposed_scale_onscreen =
1986 wxMax(proposed_scale_onscreen,
1987 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1988 g_b_overzoom_x));
1989 }
1990
1991 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1992 }
1993
1994 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1995 m_singleChart->GetChartSkew() * PI / 180.,
1996 GetVPRotation());
1997 }
1998 } // new stack
1999
2000 else // No change in Chart Stack
2001 {
2002 if ((m_bFollow) && m_singleChart)
2003 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
2004 m_singleChart->GetChartSkew() * PI / 180.,
2005 GetVPRotation());
2006 }
2007
2008update_finish:
2009
2010 // TODO
2011 // if( bNewPiano ) UpdateControlBar();
2012
2013 m_bFirstAuto = false; // Auto open on program start
2014
2015 // If we need a Refresh(), do it here...
2016 // But don't duplicate a Refresh() done by SetViewPoint()
2017 if (bNewChart && !bNewView) Refresh(false);
2018
2019#ifdef ocpnUSE_GL
2020 // If a new chart, need to invalidate gl viewport for refresh
2021 // so the fbo gets flushed
2022 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
2023#endif
2024
2025 return bNewChart | bNewView;
2026}
2027
2028void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
2029 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
2030
2031 SetQuiltRefChart(db_index);
2032 if (ChartData) {
2033 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
2034 if (pc) {
2035 if (b_autoscale) {
2036 double best_scale_ppm = GetBestVPScale(pc);
2037 SetVPScale(best_scale_ppm);
2038 }
2039 } else
2040 SetQuiltRefChart(-1);
2041 } else
2042 SetQuiltRefChart(-1);
2043}
2044
2045void ChartCanvas::SelectQuiltRefChart(int selected_index) {
2046 std::vector<int> piano_chart_index_array =
2047 GetQuiltExtendedStackdbIndexArray();
2048 int current_db_index = piano_chart_index_array[selected_index];
2049
2050 SelectQuiltRefdbChart(current_db_index);
2051}
2052
2053double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
2054 if (pchart) {
2055 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
2056
2057 if ((g_bPreserveScaleOnX) ||
2058 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2059 double new_scale_ppm = GetVPScale();
2060 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2061 } else {
2062 // This logic will bring the new chart onscreen at roughly twice the true
2063 // paper scale equivalent.
2064 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2065 double equivalent_vp_scale =
2066 GetCanvasScaleFactor() / proposed_scale_onscreen;
2067 double new_scale_ppm =
2068 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2069 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2070 }
2071
2072 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2073 // set. Otherwise, we get severe performance problems on all platforms
2074
2075 double max_underzoom_multiplier = 2.0;
2076 if (GetVP().b_quilt) {
2077 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2078 pchart->GetChartType(),
2079 pchart->GetChartFamily());
2080 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2081 }
2082
2083 proposed_scale_onscreen = wxMin(
2084 proposed_scale_onscreen,
2085 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2086 max_underzoom_multiplier);
2087
2088 // And, do not allow excessive overzoom either
2089 proposed_scale_onscreen =
2090 wxMax(proposed_scale_onscreen,
2091 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2092
2093 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2094 } else
2095 return 1.0;
2096}
2097
2098void ChartCanvas::SetupCanvasQuiltMode(void) {
2099 if (GetQuiltMode()) // going to quilt mode
2100 {
2101 ChartData->LockCache();
2102
2103 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2104
2105 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2106
2107 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2108 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2109 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2110 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2111
2112 m_Piano->SetRoundedRectangles(true);
2113
2114 // Select the proper Ref chart
2115 int target_new_dbindex = -1;
2116 if (m_pCurrentStack) {
2117 target_new_dbindex =
2118 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2119
2120 if (-1 != target_new_dbindex) {
2121 if (!IsChartQuiltableRef(target_new_dbindex)) {
2122 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2123 int type = ChartData->GetDBChartType(target_new_dbindex);
2124
2125 // walk the stack up looking for a satisfactory chart
2126 int stack_index = m_pCurrentStack->CurrentStackEntry;
2127
2128 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2129 (stack_index >= 0)) {
2130 int proj_tent = ChartData->GetDBChartProj(
2131 m_pCurrentStack->GetDBIndex(stack_index));
2132 int type_tent = ChartData->GetDBChartType(
2133 m_pCurrentStack->GetDBIndex(stack_index));
2134
2135 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2136 if ((proj == proj_tent) && (type_tent == type)) {
2137 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2138 break;
2139 }
2140 }
2141 stack_index++;
2142 }
2143 }
2144 }
2145 }
2146
2147 if (IsChartQuiltableRef(target_new_dbindex))
2148 SelectQuiltRefdbChart(target_new_dbindex,
2149 false); // Try not to allow a scale change
2150 else
2151 SelectQuiltRefdbChart(-1, false);
2152
2153 m_singleChart = NULL; // Bye....
2154
2155 // Re-qualify the quilt reference chart selection
2156 AdjustQuiltRefChart();
2157
2158 // Restore projection type saved on last quilt mode toggle
2159 // TODO
2160 // if(g_sticky_projection != -1)
2161 // GetVP().SetProjectionType(g_sticky_projection);
2162 // else
2163 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2164 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2165
2166 } else // going to SC Mode
2167 {
2168 std::vector<int> empty_array;
2169 m_Piano->SetActiveKeyArray(empty_array);
2170 m_Piano->SetNoshowIndexArray(empty_array);
2171 m_Piano->SetEclipsedIndexArray(empty_array);
2172
2173 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2174 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2175 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2176 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2177 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2178
2179 m_Piano->SetRoundedRectangles(false);
2180 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2181 }
2182
2183 // When shifting from quilt to single chart mode, select the "best" single
2184 // chart to show
2185 if (!GetQuiltMode()) {
2186 if (ChartData && ChartData->IsValid()) {
2187 UnlockQuilt();
2188
2189 double tLat, tLon;
2190 if (m_bFollow == true) {
2191 tLat = gLat;
2192 tLon = gLon;
2193 } else {
2194 tLat = m_vLat;
2195 tLon = m_vLon;
2196 }
2197
2198 if (!m_singleChart) {
2199 // Build a temporary chart stack based on tLat, tLon
2200 ChartStack TempStack;
2201 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2202 m_groupIndex);
2203
2204 // Iterate over the quilt charts actually shown, looking for the
2205 // largest scale chart that will be in the new chartstack.... This
2206 // will (almost?) always be the reference chart....
2207
2208 ChartBase *Candidate_Chart = NULL;
2209 int cur_max_scale = (int)1e8;
2210
2211 ChartBase *pChart = GetFirstQuiltChart();
2212 while (pChart) {
2213 // Is this pChart in new stack?
2214 int tEntry =
2215 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2216 if (tEntry != -1) {
2217 if (pChart->GetNativeScale() < cur_max_scale) {
2218 Candidate_Chart = pChart;
2219 cur_max_scale = pChart->GetNativeScale();
2220 }
2221 }
2222 pChart = GetNextQuiltChart();
2223 }
2224
2225 m_singleChart = Candidate_Chart;
2226
2227 // If the quilt is empty, there is no "best" chart.
2228 // So, open the smallest scale chart in the current stack
2229 if (NULL == m_singleChart) {
2230 m_singleChart = ChartData->OpenStackChartConditional(
2231 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2232 CHART_FAMILY_DONTCARE);
2233 }
2234 }
2235
2236 // Invalidate all the charts in the quilt,
2237 // as any cached data may be region based and not have fullscreen coverage
2238 InvalidateAllQuiltPatchs();
2239
2240 if (m_singleChart) {
2241 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2242 std::vector<int> one_array;
2243 one_array.push_back(dbi);
2244 m_Piano->SetActiveKeyArray(one_array);
2245 }
2246
2247 if (m_singleChart) {
2248 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2249 }
2250 }
2251 // Invalidate the current stack so that it will be rebuilt on next tick
2252 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2253 }
2254}
2255
2256bool ChartCanvas::IsTempMenuBarEnabled() {
2257#ifdef __WXMSW__
2258 int major;
2259 wxGetOsVersion(&major);
2260 return (major >
2261 5); // For Windows, function is only available on Vista and above
2262#else
2263 return true;
2264#endif
2265}
2266
2267double ChartCanvas::GetCanvasRangeMeters() {
2268 int width, height;
2269 GetSize(&width, &height);
2270 int minDimension = wxMin(width, height);
2271
2272 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2273 range *= cos(GetVP().clat * PI / 180.);
2274 return range;
2275}
2276
2277void ChartCanvas::SetCanvasRangeMeters(double range) {
2278 int width, height;
2279 GetSize(&width, &height);
2280 int minDimension = wxMin(width, height);
2281
2282 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2283 SetVPScale(scale_ppm / 2);
2284}
2285
2286bool ChartCanvas::SetUserOwnship() {
2287 // Look for user defined ownship image
2288 // This may be found in the shared data location along with other user
2289 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2290 if (pWayPointMan && pWayPointMan->DoesIconExist(_T("ownship"))) {
2291 double factor_dusk = 0.5;
2292 double factor_night = 0.25;
2293
2294 wxBitmap *pbmp = pWayPointMan->GetIconBitmap(_T("ownship"));
2295 m_pos_image_user_day = new wxImage;
2296 *m_pos_image_user_day = pbmp->ConvertToImage();
2297 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2298
2299 int gimg_width = m_pos_image_user_day->GetWidth();
2300 int gimg_height = m_pos_image_user_day->GetHeight();
2301
2302 // Make dusk and night images
2303 m_pos_image_user_dusk = new wxImage;
2304 m_pos_image_user_night = new wxImage;
2305
2306 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2307 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2308
2309 for (int iy = 0; iy < gimg_height; iy++) {
2310 for (int ix = 0; ix < gimg_width; ix++) {
2311 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2312 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2313 m_pos_image_user_day->GetGreen(ix, iy),
2314 m_pos_image_user_day->GetBlue(ix, iy));
2315 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2316 hsv.value = hsv.value * factor_dusk;
2317 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2318 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2319 nrgb.blue);
2320
2321 hsv = wxImage::RGBtoHSV(rgb);
2322 hsv.value = hsv.value * factor_night;
2323 nrgb = wxImage::HSVtoRGB(hsv);
2324 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2325 nrgb.blue);
2326 }
2327 }
2328 }
2329
2330 // Make some alternate greyed out day/dusk/night images
2331 m_pos_image_user_grey_day = new wxImage;
2332 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2333
2334 m_pos_image_user_grey_dusk = new wxImage;
2335 m_pos_image_user_grey_night = new wxImage;
2336
2337 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2338 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2339
2340 for (int iy = 0; iy < gimg_height; iy++) {
2341 for (int ix = 0; ix < gimg_width; ix++) {
2342 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2343 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2344 m_pos_image_user_grey_day->GetGreen(ix, iy),
2345 m_pos_image_user_grey_day->GetBlue(ix, iy));
2346 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2347 hsv.value = hsv.value * factor_dusk;
2348 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2349 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2350 nrgb.blue);
2351
2352 hsv = wxImage::RGBtoHSV(rgb);
2353 hsv.value = hsv.value * factor_night;
2354 nrgb = wxImage::HSVtoRGB(hsv);
2355 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2356 nrgb.blue);
2357 }
2358 }
2359 }
2360
2361 // Make a yellow image for rendering under low accuracy chart conditions
2362 m_pos_image_user_yellow_day = new wxImage;
2363 m_pos_image_user_yellow_dusk = new wxImage;
2364 m_pos_image_user_yellow_night = new wxImage;
2365
2366 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2367 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2368 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2369
2370 for (int iy = 0; iy < gimg_height; iy++) {
2371 for (int ix = 0; ix < gimg_width; ix++) {
2372 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2373 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2374 m_pos_image_user_grey_day->GetGreen(ix, iy),
2375 m_pos_image_user_grey_day->GetBlue(ix, iy));
2376
2377 // Simply remove all "blue" from the greyscaled image...
2378 // so, what is not black becomes yellow.
2379 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2380 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2381 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2382
2383 hsv = wxImage::RGBtoHSV(rgb);
2384 hsv.value = hsv.value * factor_dusk;
2385 nrgb = wxImage::HSVtoRGB(hsv);
2386 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2387
2388 hsv = wxImage::RGBtoHSV(rgb);
2389 hsv.value = hsv.value * factor_night;
2390 nrgb = wxImage::HSVtoRGB(hsv);
2391 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2392 0);
2393 }
2394 }
2395 }
2396
2397 return true;
2398 } else
2399 return false;
2400}
2401
2403 m_display_size_mm = size;
2404
2405 // int sx, sy;
2406 // wxDisplaySize( &sx, &sy );
2407
2408 // Calculate logical pixels per mm for later reference.
2409 wxSize sd = g_Platform->getDisplaySize();
2410 double horizontal = sd.x;
2411 // Set DPI (Win) scale factor
2412 g_scaler = g_Platform->GetDisplayDIPMult(this);
2413
2414 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2415 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2416
2417 if (ps52plib) {
2418 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2419 ps52plib->SetPPMM(m_pix_per_mm);
2420 }
2421
2422 wxString msg;
2423 msg.Printf(
2424 _T("Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): ")
2425 _T("%d:%d "),
2426 m_display_size_mm, sd.x, sd.y);
2427 wxLogMessage(msg);
2428
2429 int ssx, ssy;
2430 ssx = g_monitor_info[g_current_monitor].width;
2431 ssy = g_monitor_info[g_current_monitor].height;
2432 msg.Printf(_T("monitor size: %d %d"), ssx, ssy);
2433 wxLogMessage(msg);
2434
2435 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2436}
2437#if 0
2438void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2439{
2440 wxString msg(event.m_string.c_str(), wxConvUTF8);
2441 // if cpus are removed between runs
2442 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2443 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2444 }
2445
2446 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2447 {
2448 compress_msg_array.RemoveAt(event.thread);
2449 compress_msg_array.Insert( msg, event.thread);
2450 }
2451 else
2452 compress_msg_array.Add(msg);
2453
2454
2455 wxString combined_msg;
2456 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2457 combined_msg += compress_msg_array[i];
2458 combined_msg += _T("\n");
2459 }
2460
2461 bool skip = false;
2462 pprog->Update(pprog_count, combined_msg, &skip );
2463 pprog->SetSize(pprog_size);
2464 if(skip)
2465 b_skipout = skip;
2466}
2467#endif
2468void ChartCanvas::InvalidateGL() {
2469 if (!m_glcc) return;
2470#ifdef ocpnUSE_GL
2471 if (g_bopengl) m_glcc->Invalidate();
2472#endif
2473 if (m_Compass) m_Compass->UpdateStatus(true);
2474}
2475
2476int ChartCanvas::GetCanvasChartNativeScale() {
2477 int ret = 1;
2478 if (!VPoint.b_quilt) {
2479 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2480 } else
2481 ret = (int)m_pQuilt->GetRefNativeScale();
2482
2483 return ret;
2484}
2485
2486ChartBase *ChartCanvas::GetChartAtCursor() {
2487 ChartBase *target_chart;
2488 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2489 target_chart = m_singleChart;
2490 else if (VPoint.b_quilt)
2491 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2492 else
2493 target_chart = NULL;
2494 return target_chart;
2495}
2496
2497ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2498 ChartBase *target_chart;
2499 if (VPoint.b_quilt)
2500 target_chart =
2501 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2502 else
2503 target_chart = NULL;
2504 return target_chart;
2505}
2506
2507int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2508 int new_dbIndex = -1;
2509 if (!VPoint.b_quilt) {
2510 if (m_pCurrentStack) {
2511 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2512 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2513 if (sc >= scale) {
2514 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2515 break;
2516 }
2517 }
2518 }
2519 } else {
2520 // Using the current quilt, select a useable reference chart
2521 // Said chart will be in the extended (possibly full-screen) stack,
2522 // And will have a scale equal to or just greater than the stipulated
2523 // value
2524 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2525 if (im > 0) {
2526 for (unsigned int is = 0; is < im; is++) {
2527 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2528 m_pQuilt->GetExtendedStackIndexArray()[is]);
2529 if ((m.Scale_ge(
2530 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2531 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2532 break;
2533 }
2534 }
2535 }
2536 }
2537
2538 return new_dbIndex;
2539}
2540
2541void ChartCanvas::EnablePaint(bool b_enable) {
2542 m_b_paint_enable = b_enable;
2543#ifdef ocpnUSE_GL
2544 if (m_glcc) m_glcc->EnablePaint(b_enable);
2545#endif
2546}
2547
2548bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2549
2550void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2551
2552std::vector<int> ChartCanvas::GetQuiltIndexArray(void) {
2553 return m_pQuilt->GetQuiltIndexArray();
2554 ;
2555}
2556
2557void ChartCanvas::SetQuiltMode(bool b_quilt) {
2558 VPoint.b_quilt = b_quilt;
2559 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2560}
2561
2562bool ChartCanvas::GetQuiltMode(void) { return VPoint.b_quilt; }
2563
2564int ChartCanvas::GetQuiltReferenceChartIndex(void) {
2565 return m_pQuilt->GetRefChartdbIndex();
2566}
2567
2568void ChartCanvas::InvalidateAllQuiltPatchs(void) {
2569 m_pQuilt->InvalidateAllQuiltPatchs();
2570}
2571
2572ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2573 return m_pQuilt->GetLargestScaleChart();
2574}
2575
2576ChartBase *ChartCanvas::GetFirstQuiltChart() {
2577 return m_pQuilt->GetFirstChart();
2578}
2579
2580ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2581
2582int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2583
2584void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2585 m_pQuilt->SetHiliteIndex(dbIndex);
2586}
2587
2588void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2589 m_pQuilt->SetHiliteIndexArray(hilite_array);
2590}
2591
2592void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2593 m_pQuilt->ClearHiliteIndexArray();
2594}
2595
2596std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2597 bool flag2) {
2598 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2599}
2600
2601int ChartCanvas::GetQuiltRefChartdbIndex(void) {
2602 return m_pQuilt->GetRefChartdbIndex();
2603}
2604
2605std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2606 return m_pQuilt->GetExtendedStackIndexArray();
2607}
2608
2609std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2610 return m_pQuilt->GetFullscreenIndexArray();
2611}
2612
2613std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2614 return m_pQuilt->GetEclipsedStackIndexArray();
2615}
2616
2617void ChartCanvas::InvalidateQuilt(void) { return m_pQuilt->Invalidate(); }
2618
2619double ChartCanvas::GetQuiltMaxErrorFactor() {
2620 return m_pQuilt->GetMaxErrorFactor();
2621}
2622
2623bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2624 return m_pQuilt->IsChartQuiltableRef(db_index);
2625}
2626
2627bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2628 double chartMaxScale =
2629 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2630 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2631}
2632
2633void ChartCanvas::StartMeasureRoute() {
2634 if (!m_routeState) { // no measure tool if currently creating route
2635 if (m_bMeasure_Active) {
2636 g_pRouteMan->DeleteRoute(m_pMeasureRoute,
2637 NavObjectChanges::getInstance());
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, NavObjectChanges::getInstance());
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 double t0 = wxGetLocalTimeMillis().ToDouble();
2920 pConfig->Flush();
2921 double t1 = wxGetLocalTimeMillis().ToDouble() - t0;
2922
2923 ToggleCanvasQuiltMode();
2924 auto &noteman = NotificationManager::GetInstance();
2925 noteman.AddNotification(NotificationSeverity::kCritical,
2926 "Test Notification long message.\nMultiline "
2927 "message that may be many, many chars wide.");
2928
2929 break;
2930 }
2931#endif
2932
2933 case WXK_F11:
2934 parent_frame->ToggleFullScreen();
2935 b_handled = true;
2936 break;
2937
2938 case WXK_F12: {
2939 if (m_modkeys == wxMOD_ALT) {
2940 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2941 // testing
2942 bool b = GetEnableTenHertzUpdate();
2944 UpdateGPSCompassStatusBox(true);
2945 auto &noteman = NotificationManager::GetInstance();
2946 noteman.AddNotification(NotificationSeverity::kInformational,
2947 "Test Timed Notification", 10);
2948 } else {
2949 ToggleChartOutlines();
2950 }
2951 break;
2952 }
2953
2954 case WXK_PAUSE: // Drop MOB
2955 parent_frame->ActivateMOB();
2956 break;
2957
2958 // NUMERIC PAD
2959 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2960 case WXK_PAGEUP: {
2961 ZoomCanvas(g_plus_minus_zoom_factor, false);
2962 break;
2963 }
2964 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2965 case WXK_PAGEDOWN: {
2966 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2967 break;
2968 }
2969 case WXK_DELETE:
2970 case WXK_BACK:
2971 if (m_bMeasure_Active) {
2972 if (m_nMeasureState > 2) {
2973 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2974 m_pMeasureRoute->m_lastMousePointIndex =
2975 m_pMeasureRoute->GetnPoints();
2976 m_nMeasureState--;
2977 gFrame->RefreshAllCanvas();
2978 } else {
2979 CancelMeasureRoute();
2980 StartMeasureRoute();
2981 }
2982 }
2983 break;
2984 default:
2985 break;
2986 }
2987
2988 if (event.GetKeyCode() < 128) // ascii
2989 {
2990 int key_char = event.GetKeyCode();
2991
2992 // Handle both QWERTY and AZERTY keyboard separately for a few control
2993 // codes
2994 if (!g_b_assume_azerty) {
2995#ifdef __WXMAC__
2996 if (g_benable_rotate) {
2997 switch (key_char) {
2998 // On other platforms these are handled in OnKeyChar, which
2999 // (apparently) works better in some locales. On OS X it is better
3000 // to handle them here, since pressing Alt (which should change the
3001 // rotation speed) changes the key char and so prevents the keys
3002 // from working.
3003 case ']':
3004 RotateCanvas(1);
3005 b_handled = true;
3006 break;
3007
3008 case '[':
3009 RotateCanvas(-1);
3010 b_handled = true;
3011 break;
3012
3013 case '\\':
3014 DoRotateCanvas(0);
3015 b_handled = true;
3016 break;
3017 }
3018 }
3019#endif
3020 } else { // AZERTY
3021 switch (key_char) {
3022 case 43:
3023 ZoomCanvas(g_plus_minus_zoom_factor, false);
3024 break;
3025
3026 case 54: // '-' alpha/num pad
3027 // case 56: // '_' alpha/num pad
3028 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
3029 break;
3030 }
3031 }
3032
3033#ifdef __WXOSX__
3034 // Ctrl+Cmd+F toggles fullscreen on macOS
3035 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
3036 m_modkeys & wxMOD_RAW_CONTROL) {
3037 parent_frame->ToggleFullScreen();
3038 return;
3039 }
3040#endif
3041
3042 if (event.ControlDown()) key_char -= 64;
3043
3044 if (key_char >= '0' && key_char <= '9')
3045 SetGroupIndex(key_char - '0');
3046 else
3047
3048 switch (key_char) {
3049 case 'A':
3050 SetShowENCAnchor(!GetShowENCAnchor());
3051 ReloadVP();
3052
3053 break;
3054
3055 case 'C':
3056 parent_frame->ToggleColorScheme();
3057 break;
3058
3059 case 'D': {
3060 int x, y;
3061 event.GetPosition(&x, &y);
3062 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3063 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3064 // First find out what kind of chart is being used
3065 if (!pPopupDetailSlider) {
3066 if (VPoint.b_quilt) {
3067 if (m_pQuilt) {
3068 if (m_pQuilt->GetChartAtPix(
3069 VPoint,
3070 wxPoint(
3071 x, y))) // = null if no chart loaded for this point
3072 {
3073 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3074 ->GetChartType();
3075 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3076 ->GetChartFamily();
3077 }
3078 }
3079 } else {
3080 if (m_singleChart) {
3081 ChartType = m_singleChart->GetChartType();
3082 ChartFam = m_singleChart->GetChartFamily();
3083 }
3084 }
3085 // If a charttype is found show the popupslider
3086 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3087 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3088 pPopupDetailSlider = new PopUpDSlide(
3089 this, -1, ChartType, ChartFam,
3090 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3091 wxDefaultSize, wxSIMPLE_BORDER, _T(""));
3092 if (pPopupDetailSlider) pPopupDetailSlider->Show();
3093 }
3094 } else //( !pPopupDetailSlider ) close popupslider
3095 {
3096 if (pPopupDetailSlider) pPopupDetailSlider->Close();
3097 pPopupDetailSlider = NULL;
3098 }
3099 break;
3100 }
3101
3102 case 'E':
3103 m_nmea_log->Show();
3104 m_nmea_log->Raise();
3105 break;
3106
3107 case 'L':
3108 SetShowENCLights(!GetShowENCLights());
3109 ReloadVP();
3110
3111 break;
3112
3113 case 'M':
3114 if (event.ShiftDown())
3115 m_bMeasure_DistCircle = true;
3116 else
3117 m_bMeasure_DistCircle = false;
3118
3119 StartMeasureRoute();
3120 break;
3121
3122 case 'N':
3123 if (g_bInlandEcdis && ps52plib) {
3124 SetENCDisplayCategory((_DisCat)STANDARD);
3125 }
3126 break;
3127
3128 case 'O':
3129 ToggleChartOutlines();
3130 break;
3131
3132 case 'Q':
3133 ToggleCanvasQuiltMode();
3134 break;
3135
3136 case 'P':
3137 parent_frame->ToggleTestPause();
3138 break;
3139 case 'R':
3140 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3141 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3142 g_iNavAidRadarRingsNumberVisible = 1;
3143 else if (!g_bNavAidRadarRingsShown &&
3144 g_iNavAidRadarRingsNumberVisible == 1)
3145 g_iNavAidRadarRingsNumberVisible = 0;
3146 break;
3147 case 'S':
3148 SetShowENCDepth(!m_encShowDepth);
3149 ReloadVP();
3150 break;
3151
3152 case 'T':
3153 SetShowENCText(!GetShowENCText());
3154 ReloadVP();
3155 break;
3156
3157 case 'U':
3158 SetShowENCDataQual(!GetShowENCDataQual());
3159 ReloadVP();
3160 break;
3161
3162 case 'V':
3163 m_bShowNavobjects = !m_bShowNavobjects;
3164 Refresh(true);
3165 break;
3166
3167 case 'W': // W Toggle CPA alarm
3168 ToggleCPAWarn();
3169
3170 break;
3171
3172 case 1: // Ctrl A
3173 TogglebFollow();
3174
3175 break;
3176
3177 case 2: // Ctrl B
3178 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3179 break;
3180
3181 case 13: // Ctrl M // Drop Marker at cursor
3182 {
3183 if (event.ControlDown()) gFrame->DropMarker(false);
3184 break;
3185 }
3186
3187 case 14: // Ctrl N - Activate next waypoint in a route
3188 {
3189 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3190 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3191 if ((indexActive + 1) <= r->GetnPoints()) {
3192 g_pRouteMan->ActivateNextPoint(r, true);
3193 InvalidateGL();
3194 Refresh(false);
3195 }
3196 }
3197 break;
3198 }
3199
3200 case 15: // Ctrl O - Drop Marker at boat's position
3201 {
3202 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3203 break;
3204 }
3205
3206 case 32: // Special needs use space bar
3207 {
3208 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3209 break;
3210 }
3211
3212 case -32: // Ctrl Space // Drop MOB
3213 {
3214 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3215
3216 break;
3217 }
3218
3219 case -20: // Ctrl ,
3220 {
3221 parent_frame->DoSettings();
3222 break;
3223 }
3224 case 17: // Ctrl Q
3225 parent_frame->Close();
3226 return;
3227
3228 case 18: // Ctrl R
3229 StartRoute();
3230 return;
3231
3232 case 20: // Ctrl T
3233 if (NULL == pGoToPositionDialog) // There is one global instance of
3234 // the Go To Position Dialog
3235 pGoToPositionDialog = new GoToPositionDialog(this);
3236 pGoToPositionDialog->SetCanvas(this);
3237 pGoToPositionDialog->Show();
3238 break;
3239
3240 case 25: // Ctrl Y
3241 if (undo->AnythingToRedo()) {
3242 undo->RedoNextAction();
3243 InvalidateGL();
3244 Refresh(false);
3245 }
3246 break;
3247
3248 case 26:
3249 if (event.ShiftDown()) { // Shift-Ctrl-Z
3250 if (undo->AnythingToRedo()) {
3251 undo->RedoNextAction();
3252 InvalidateGL();
3253 Refresh(false);
3254 }
3255 } else { // Ctrl Z
3256 if (undo->AnythingToUndo()) {
3257 undo->UndoLastAction();
3258 InvalidateGL();
3259 Refresh(false);
3260 }
3261 }
3262 break;
3263
3264 case 27:
3265 // Generic break
3266 if (m_bMeasure_Active) {
3267 CancelMeasureRoute();
3268
3269 SetCursor(*pCursorArrow);
3270
3271 // SurfaceToolbar();
3272 gFrame->RefreshAllCanvas();
3273 }
3274
3275 if (m_routeState) // creating route?
3276 {
3277 FinishRoute();
3278 // SurfaceToolbar();
3279 InvalidateGL();
3280 Refresh(false);
3281 }
3282
3283 break;
3284
3285 case 7: // Ctrl G
3286 switch (gamma_state) {
3287 case (0):
3288 r_gamma_mult = 0;
3289 g_gamma_mult = 1;
3290 b_gamma_mult = 0;
3291 gamma_state = 1;
3292 break;
3293 case (1):
3294 r_gamma_mult = 1;
3295 g_gamma_mult = 0;
3296 b_gamma_mult = 0;
3297 gamma_state = 2;
3298 break;
3299 case (2):
3300 r_gamma_mult = 1;
3301 g_gamma_mult = 1;
3302 b_gamma_mult = 1;
3303 gamma_state = 0;
3304 break;
3305 }
3306 SetScreenBrightness(g_nbrightness);
3307
3308 break;
3309
3310 case 9: // Ctrl I
3311 if (event.ControlDown()) {
3312 m_bShowCompassWin = !m_bShowCompassWin;
3313 SetShowGPSCompassWindow(m_bShowCompassWin);
3314 Refresh(false);
3315 }
3316 break;
3317
3318 default:
3319 break;
3320
3321 } // switch
3322 }
3323
3324 // Allow OnKeyChar to catch the key events too.
3325 if (!b_handled) {
3326 event.Skip();
3327 }
3328}
3329
3330void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3331 if (SendKeyEventToPlugins(event))
3332 return; // PlugIn did something, and does not want the canvas to do
3333 // anything else
3334
3335 switch (event.GetKeyCode()) {
3336 case WXK_TAB:
3337 parent_frame->SwitchKBFocus(this);
3338 break;
3339
3340 case WXK_LEFT:
3341 case WXK_RIGHT:
3342 m_panx = 0;
3343 if (!m_pany) m_panspeed = 0;
3344 break;
3345
3346 case WXK_UP:
3347 case WXK_DOWN:
3348 m_pany = 0;
3349 if (!m_panx) m_panspeed = 0;
3350 break;
3351
3352 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3353 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3354 case WXK_PAGEUP:
3355 case WXK_PAGEDOWN:
3356 if (m_mustmove) DoMovement(m_mustmove);
3357
3358 m_zoom_factor = 1;
3359 break;
3360
3361 case WXK_ALT:
3362 m_modkeys &= ~wxMOD_ALT;
3363#ifdef OCPN_ALT_MENUBAR
3364#ifndef __WXOSX__
3365 // If the permanent menu bar is disabled, and we are not in the middle of
3366 // another key combo, then show the menu bar temporarily when Alt is
3367 // released (or hide it if already visible).
3368 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3369 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3370 parent_frame->ApplyGlobalSettings(false);
3371 }
3372 m_bMayToggleMenuBar = true;
3373#endif
3374#endif
3375 break;
3376
3377 case WXK_CONTROL:
3378 m_modkeys &= ~wxMOD_CONTROL;
3379 break;
3380 }
3381
3382 if (event.GetKeyCode() < 128) // ascii
3383 {
3384 int key_char = event.GetKeyCode();
3385
3386 // Handle both QWERTY and AZERTY keyboard separately for a few control
3387 // codes
3388 if (!g_b_assume_azerty) {
3389 switch (key_char) {
3390 case '+':
3391 case '=':
3392 case '-':
3393 case '_':
3394 case 54:
3395 case 56: // '_' alpha/num pad
3396 DoMovement(m_mustmove);
3397
3398 // m_zoom_factor = 1;
3399 break;
3400 case '[':
3401 case ']':
3402 DoMovement(m_mustmove);
3403 m_rotation_speed = 0;
3404 break;
3405 }
3406 } else {
3407 switch (key_char) {
3408 case 43:
3409 case 54: // '-' alpha/num pad
3410 case 56: // '_' alpha/num pad
3411 DoMovement(m_mustmove);
3412
3413 m_zoom_factor = 1;
3414 break;
3415 }
3416 }
3417 }
3418 event.Skip();
3419}
3420
3421void ChartCanvas::ToggleChartOutlines(void) {
3422 m_bShowOutlines = !m_bShowOutlines;
3423
3424 Refresh(false);
3425
3426#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3427 // needs a full refresh
3428 if (g_bopengl) InvalidateGL();
3429#endif
3430}
3431
3432void ChartCanvas::ToggleLookahead() {
3433 m_bLookAhead = !m_bLookAhead;
3434 m_OSoffsetx = 0; // center ownship
3435 m_OSoffsety = 0;
3436}
3437
3438void ChartCanvas::SetUpMode(int mode) {
3439 m_upMode = mode;
3440
3441 if (mode != NORTH_UP_MODE) {
3442 // Stuff the COGAvg table in case COGUp is selected
3443 double stuff = 0;
3444 if (!std::isnan(gCog)) stuff = gCog;
3445
3446 if (g_COGAvgSec > 0) {
3447 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3448 }
3449 g_COGAvg = stuff;
3450 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3451 } else {
3452 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3453 SetVPRotation(GetVPSkew());
3454 else
3455 SetVPRotation(0); /* reset to north up */
3456 }
3457
3458 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3459 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3460
3461 UpdateGPSCompassStatusBox(true);
3462 gFrame->DoChartUpdate();
3463}
3464
3465bool ChartCanvas::DoCanvasCOGSet(void) {
3466 if (GetUpMode() == NORTH_UP_MODE) return false;
3467 double cog_use = g_COGAvg;
3468 if (g_btenhertz) cog_use = gCog;
3469
3470 double rotation = 0;
3471 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3472 rotation = -gHdt * PI / 180.;
3473 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3474 rotation = -cog_use * PI / 180.;
3475
3476 SetVPRotation(rotation);
3477 return true;
3478}
3479
3480double easeOutCubic(double t) {
3481 // Starts quickly and slows down toward the end
3482 return 1.0 - pow(1.0 - t, 3.0);
3483}
3484
3485void ChartCanvas::StartChartDragInertia() {
3486 //
3487 // printf("\nStart ChartDragInertia\n");
3488 m_bChartDragging = false;
3489
3490 // Set some parameters
3491 m_chart_drag_inertia_time = 750; // msec
3492 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3493 m_last_elapsed = 0;
3494
3495 // Calculate ending drag velocity
3496 size_t n_vel = 10;
3497 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3498 int xacc = 0;
3499 int yacc = 0;
3500 double tacc = 0;
3501 size_t length = m_drag_vec_t.size();
3502 for (size_t i = 0; i < n_vel; i++) {
3503 xacc += m_drag_vec_x.at(length - 1 - i);
3504 yacc += m_drag_vec_y.at(length - 1 - i);
3505 tacc += m_drag_vec_t.at(length - 1 - i);
3506 // printf("%d %g\n", xacc, tacc);
3507 }
3508 m_chart_drag_velocity_x = xacc / tacc;
3509 m_chart_drag_velocity_y = yacc / tacc;
3510
3511 m_chart_drag_inertia_active = true;
3512
3513 // First callback as fast as possible.
3514 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3515
3516 // printf(" Drag parms %d %d %g\n", m_chart_drag_total_x,
3517 // m_chart_drag_total_y, m_chart_drag_total_time);
3518}
3519
3520void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3521 if (!m_chart_drag_inertia_active) return;
3522
3523 // Calculate time fraction from 0..1
3524 wxLongLong now = wxGetLocalTimeMillis();
3525 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3526 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3527 if (t > 1.0) t = 1.0;
3528 double e = 1.0 - easeOutCubic(t); // 0..1
3529
3530 double dx =
3531 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3532 double dy =
3533 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3534
3535 // double distance = pow((pow(dx, 2) + pow(dy, 2)), 0.5);
3536 // printf(" %5g %5g %5g %5g pix/sec\n", elapsed,
3537 // elapsed - m_last_elapsed, distance, distance * 1000 / elapsed);
3538
3539 m_last_elapsed = elapsed;
3540
3541 // Ensure that target destination lies on whole-pixel boundary
3542 // This allows the render engine to use a faster FBO copy method for drawing
3543 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3544 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3545 double inertia_lat, inertia_lon;
3546 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3547 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3548
3549 Refresh(false);
3550
3551 // Stop condition
3552 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3553 m_chart_drag_inertia_timer.Stop();
3554 m_chart_drag_inertia_active = false;
3555
3556 // Disable chart pan movement logic
3557 m_target_lat = GetVP().clat;
3558 m_target_lon = GetVP().clon;
3559 m_pan_drag.x = m_pan_drag.y = 0;
3560 m_panx = m_pany = 0;
3561
3562 } else {
3563 int target_redraw_interval = 40; // msec
3564 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3565 }
3566}
3567
3568void ChartCanvas::StopMovement() {
3569 m_panx = m_pany = 0;
3570 m_panspeed = 0;
3571 m_zoom_factor = 1;
3572 m_rotation_speed = 0;
3573 m_mustmove = 0;
3574#if 0
3575#if !defined(__WXGTK__) && !defined(__WXQT__)
3576 SetFocus();
3577 gFrame->Raise();
3578#endif
3579#endif
3580}
3581
3582/* instead of integrating in timer callbacks
3583 (which do not always get called fast enough)
3584 we can perform the integration of movement
3585 at each render frame based on the time change */
3586bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3587 // Start/restart the stop movement timer
3588 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3589
3590 if (!pMovementTimer->IsRunning()) {
3591 // printf("timer not running, starting\n");
3592 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3593 }
3594
3595 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3596 // already moving, gets called again because of key-repeat event
3597 return false;
3598 }
3599
3600 m_last_movement_time = wxDateTime::UNow();
3601
3602 return true;
3603}
3604void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3605 int nstep) {
3606 // Save the target
3607 m_target_lat = target_lat;
3608 m_target_lon = target_lon;
3609
3610 // Save the start point
3611 m_start_lat = GetVP().clat;
3612 m_start_lon = GetVP().clon;
3613
3614 m_VPMovementTimer.Start(1, true); // oneshot
3615 m_timed_move_vp_active = true;
3616 m_stvpc = 0;
3617 m_timedVP_step = nstep;
3618}
3619
3620void ChartCanvas::DoTimedMovementVP() {
3621 if (!m_timed_move_vp_active) return; // not active
3622 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3623 StopMovement();
3624 return;
3625 }
3626 // Stop condition
3627 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3628 double d2 =
3629 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3630 d2 = pow(d2, 0.5);
3631
3632 if (d2 < one_pix) {
3633 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3634 StopMovementVP();
3635 return;
3636 }
3637
3638 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3639 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3640 // StopMovementVP();
3641 // return;
3642 // }
3643
3644 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3645 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3646
3647 m_run_lat = new_lat;
3648 m_run_lon = new_lon;
3649
3650 // printf(" Timed\n");
3651 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3652}
3653
3654void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3655
3656void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3657
3658void ChartCanvas::StartTimedMovementTarget() {}
3659
3660void ChartCanvas::DoTimedMovementTarget() {}
3661
3662void ChartCanvas::StopMovementTarget() {}
3663
3664void ChartCanvas::DoTimedMovement() {
3665 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3666 !m_rotation_speed)
3667 return; /* not moving */
3668
3669 wxDateTime now = wxDateTime::UNow();
3670 long dt = 0;
3671 if (m_last_movement_time.IsValid())
3672 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3673
3674 m_last_movement_time = now;
3675
3676 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3677 dt = 500;
3678
3679 DoMovement(dt);
3680}
3681
3682void ChartCanvas::DoMovement(long dt) {
3683 /* if we get here quickly assume 1ms so that some movement occurs */
3684 if (dt == 0) dt = 1;
3685
3686 m_mustmove -= dt;
3687 if (m_mustmove < 0) m_mustmove = 0;
3688
3689 if (m_pan_drag.x || m_pan_drag.y) {
3690 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3691 m_pan_drag.x = m_pan_drag.y = 0;
3692 }
3693
3694 if (m_panx || m_pany) {
3695 const double slowpan = .1, maxpan = 2;
3696 if (m_modkeys == wxMOD_ALT)
3697 m_panspeed = slowpan;
3698 else {
3699 m_panspeed += (double)dt / 500; /* apply acceleration */
3700 m_panspeed = wxMin(maxpan, m_panspeed);
3701 }
3702 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3703 }
3704
3705 if (m_zoom_factor != 1) {
3706 double alpha = 400, beta = 1.5;
3707 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3708
3709 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3710
3711 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3712
3713 // Try to hit the zoom target exactly.
3714 // if(m_wheelzoom_stop_oneshot > 0)
3715 {
3716 if (zoom_factor > 1) {
3717 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3718 zoom_factor = VPoint.chart_scale / m_zoom_target;
3719 }
3720
3721 else if (zoom_factor < 1) {
3722 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3723 zoom_factor = VPoint.chart_scale / m_zoom_target;
3724 }
3725 }
3726
3727 if (fabs(zoom_factor - 1) > 1e-4)
3728 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3729
3730 if (m_wheelzoom_stop_oneshot > 0) {
3731 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3732 m_wheelzoom_stop_oneshot = 0;
3733 StopMovement();
3734 }
3735
3736 // Don't overshoot the zoom target.
3737 if (zoom_factor > 1) {
3738 if (VPoint.chart_scale <= m_zoom_target) {
3739 m_wheelzoom_stop_oneshot = 0;
3740 StopMovement();
3741 }
3742 } else if (zoom_factor < 1) {
3743 if (VPoint.chart_scale >= m_zoom_target) {
3744 m_wheelzoom_stop_oneshot = 0;
3745 StopMovement();
3746 }
3747 }
3748 }
3749 }
3750
3751 if (m_rotation_speed) { /* in degrees per second */
3752 double speed = m_rotation_speed;
3753 if (m_modkeys == wxMOD_ALT) speed /= 10;
3754 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3755 }
3756}
3757
3758void ChartCanvas::SetColorScheme(ColorScheme cs) {
3759 SetAlertString(_T(""));
3760
3761 // Setup ownship image pointers
3762 switch (cs) {
3763 case GLOBAL_COLOR_SCHEME_DAY:
3764 m_pos_image_red = &m_os_image_red_day;
3765 m_pos_image_grey = &m_os_image_grey_day;
3766 m_pos_image_yellow = &m_os_image_yellow_day;
3767 m_pos_image_user = m_pos_image_user_day;
3768 m_pos_image_user_grey = m_pos_image_user_grey_day;
3769 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3770 m_cTideBitmap = m_bmTideDay;
3771 m_cCurrentBitmap = m_bmCurrentDay;
3772
3773 break;
3774 case GLOBAL_COLOR_SCHEME_DUSK:
3775 m_pos_image_red = &m_os_image_red_dusk;
3776 m_pos_image_grey = &m_os_image_grey_dusk;
3777 m_pos_image_yellow = &m_os_image_yellow_dusk;
3778 m_pos_image_user = m_pos_image_user_dusk;
3779 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3780 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3781 m_cTideBitmap = m_bmTideDusk;
3782 m_cCurrentBitmap = m_bmCurrentDusk;
3783 break;
3784 case GLOBAL_COLOR_SCHEME_NIGHT:
3785 m_pos_image_red = &m_os_image_red_night;
3786 m_pos_image_grey = &m_os_image_grey_night;
3787 m_pos_image_yellow = &m_os_image_yellow_night;
3788 m_pos_image_user = m_pos_image_user_night;
3789 m_pos_image_user_grey = m_pos_image_user_grey_night;
3790 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3791 m_cTideBitmap = m_bmTideNight;
3792 m_cCurrentBitmap = m_bmCurrentNight;
3793 break;
3794 default:
3795 m_pos_image_red = &m_os_image_red_day;
3796 m_pos_image_grey = &m_os_image_grey_day;
3797 m_pos_image_yellow = &m_os_image_yellow_day;
3798 m_pos_image_user = m_pos_image_user_day;
3799 m_pos_image_user_grey = m_pos_image_user_grey_day;
3800 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3801 m_cTideBitmap = m_bmTideDay;
3802 m_cCurrentBitmap = m_bmCurrentDay;
3803 break;
3804 }
3805
3806 CreateDepthUnitEmbossMaps(cs);
3807 CreateOZEmbossMapData(cs);
3808
3809 // Set up fog effect base color
3810 m_fog_color = wxColor(
3811 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3812 float dim = 1.0;
3813 switch (cs) {
3814 case GLOBAL_COLOR_SCHEME_DUSK:
3815 dim = 0.5;
3816 break;
3817 case GLOBAL_COLOR_SCHEME_NIGHT:
3818 dim = 0.25;
3819 break;
3820 default:
3821 break;
3822 }
3823 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3824 m_fog_color.Blue() * dim);
3825
3826 // Really dark
3827#if 0
3828 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3829 SetBackgroundColour( wxColour(0,0,0) );
3830
3831 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3832 }
3833 else{
3834 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3835#ifndef __WXMAC__
3836 SetBackgroundColour( wxNullColour );
3837#endif
3838 }
3839#endif
3840
3841 // UpdateToolbarColorScheme(cs);
3842
3843 m_Piano->SetColorScheme(cs);
3844
3845 m_Compass->SetColorScheme(cs);
3846
3847 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3848
3849 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3850#ifdef ocpnUSE_GL
3851 if (g_bopengl && m_glcc) {
3852 m_glcc->SetColorScheme(cs);
3853 g_glTextureManager->ClearAllRasterTextures();
3854 // m_glcc->FlushFBO();
3855 }
3856#endif
3857 SetbTCUpdate(true); // force re-render of tide/current locators
3858 m_brepaint_piano = true;
3859
3860 ReloadVP();
3861
3862 m_cs = cs;
3863}
3864
3865wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3866 wxImage img = Bitmap.ConvertToImage();
3867 int sx = img.GetWidth();
3868 int sy = img.GetHeight();
3869
3870 wxImage new_img(img);
3871
3872 for (int i = 0; i < sx; i++) {
3873 for (int j = 0; j < sy; j++) {
3874 if (!img.IsTransparent(i, j)) {
3875 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3876 (unsigned char)(img.GetGreen(i, j) * factor),
3877 (unsigned char)(img.GetBlue(i, j) * factor));
3878 }
3879 }
3880 }
3881
3882 wxBitmap ret = wxBitmap(new_img);
3883
3884 return ret;
3885}
3886
3887void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3888 int max) {
3889 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3890 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3891
3892 if (!m_pBrightPopup) {
3893 // Calculate size
3894 int x, y;
3895 GetTextExtent(_T("MAX"), &x, &y, NULL, NULL, pfont);
3896
3897 m_pBrightPopup = new TimedPopupWin(this, 3);
3898
3899 m_pBrightPopup->SetSize(x, y);
3900 m_pBrightPopup->Move(120, 120);
3901 }
3902
3903 int bmpsx = m_pBrightPopup->GetSize().x;
3904 int bmpsy = m_pBrightPopup->GetSize().y;
3905
3906 wxBitmap bmp(bmpsx, bmpsx);
3907 wxMemoryDC mdc(bmp);
3908
3909 mdc.SetTextForeground(GetGlobalColor(_T("GREEN4")));
3910 mdc.SetBackground(wxBrush(GetGlobalColor(_T("UINFD"))));
3911 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3912 mdc.SetBrush(wxBrush(GetGlobalColor(_T("UINFD"))));
3913 mdc.Clear();
3914
3915 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3916
3917 mdc.SetFont(*pfont);
3918 wxString val;
3919
3920 if (brightness == max)
3921 val = _T("MAX");
3922 else if (brightness == min)
3923 val = _T("MIN");
3924 else
3925 val.Printf(_T("%3d"), brightness);
3926
3927 mdc.DrawText(val, 0, 0);
3928
3929 mdc.SelectObject(wxNullBitmap);
3930
3931 m_pBrightPopup->SetBitmap(bmp);
3932 m_pBrightPopup->Show();
3933 m_pBrightPopup->Refresh();
3934}
3935
3936void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3937 m_b_rot_hidef = true;
3938 ReloadVP();
3939}
3940
3941void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3942 if (!g_bRollover) return;
3943
3944 bool b_need_refresh = false;
3945
3946 wxSize win_size = GetSize() * m_displayScale;
3947 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3948
3949 // Handle the AIS Rollover Window first
3950 bool showAISRollover = false;
3951 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3952 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3953 SelectItem *pFind = pSelectAIS->FindSelection(
3954 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3955 if (pFind) {
3956 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3957 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3958
3959 if (ptarget) {
3960 showAISRollover = true;
3961
3962 if (NULL == m_pAISRolloverWin) {
3963 m_pAISRolloverWin = new RolloverWin(this);
3964 m_pAISRolloverWin->IsActive(false);
3965 b_need_refresh = true;
3966 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3967 m_AISRollover_MMSI != FoundAIS_MMSI) {
3968 // Sometimes the mouse moves fast enough to get over a new AIS
3969 // target before the one-shot has fired to remove the old target.
3970 // Result: wrong target data is shown.
3971 // Detect this case,close the existing rollover ASAP, and restart
3972 // the timer.
3973 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3974 m_pAISRolloverWin->IsActive(false);
3975 m_AISRollover_MMSI = 0;
3976 Refresh();
3977 return;
3978 }
3979
3980 m_AISRollover_MMSI = FoundAIS_MMSI;
3981
3982 if (!m_pAISRolloverWin->IsActive()) {
3983 wxString s = ptarget->GetRolloverString();
3984 m_pAISRolloverWin->SetString(s);
3985
3986 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3987 AIS_ROLLOVER, win_size);
3988 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3989 m_pAISRolloverWin->IsActive(true);
3990 b_need_refresh = true;
3991 }
3992 }
3993 } else {
3994 m_AISRollover_MMSI = 0;
3995 showAISRollover = false;
3996 }
3997 }
3998
3999 // Maybe turn the rollover off
4000 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
4001 m_pAISRolloverWin->IsActive(false);
4002 m_AISRollover_MMSI = 0;
4003 b_need_refresh = true;
4004 }
4005
4006 // Now the Route info rollover
4007 // Show the route segment info
4008 bool showRouteRollover = false;
4009
4010 if (NULL == m_pRolloverRouteSeg) {
4011 // Get a list of all selectable sgements, and search for the first
4012 // visible segment as the rollover target.
4013
4014 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4015 SelectableItemList SelList = pSelect->FindSelectionList(
4016 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
4017 wxSelectableItemListNode *node = SelList.GetFirst();
4018 while (node) {
4019 SelectItem *pFindSel = node->GetData();
4020
4021 Route *pr = (Route *)pFindSel->m_pData3; // candidate
4022
4023 if (pr && pr->IsVisible()) {
4024 m_pRolloverRouteSeg = pFindSel;
4025 showRouteRollover = true;
4026
4027 if (NULL == m_pRouteRolloverWin) {
4028 m_pRouteRolloverWin = new RolloverWin(this, 10);
4029 m_pRouteRolloverWin->IsActive(false);
4030 }
4031
4032 if (!m_pRouteRolloverWin->IsActive()) {
4033 wxString s;
4034 RoutePoint *segShow_point_a =
4035 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4036 RoutePoint *segShow_point_b =
4037 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4038
4039 double brg, dist;
4040 DistanceBearingMercator(
4041 segShow_point_b->m_lat, segShow_point_b->m_lon,
4042 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4043
4044 if (!pr->m_bIsInLayer)
4045 s.Append(_("Route") + _T(": "));
4046 else
4047 s.Append(_("Layer Route: "));
4048
4049 if (pr->m_RouteNameString.IsEmpty())
4050 s.Append(_("(unnamed)"));
4051 else
4052 s.Append(pr->m_RouteNameString);
4053
4054 s << _T("\n") << _("Total Length: ")
4055 << FormatDistanceAdaptive(pr->m_route_length) << _T("\n")
4056 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4057 << segShow_point_b->GetName() << _T("\n");
4058
4059 if (g_bShowTrue)
4060 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4061 (int)floor(brg + 0.5), 0x00B0);
4062 if (g_bShowMag) {
4063 double latAverage =
4064 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4065 double lonAverage =
4066 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4067 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4068
4069 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4070 (int)floor(varBrg + 0.5), 0x00B0);
4071 }
4072
4073 s << FormatDistanceAdaptive(dist);
4074
4075 // Compute and display cumulative distance from route start point to
4076 // current leg end point and RNG,TTG,ETA from ship to current leg end
4077 // point for active route
4078 double shiptoEndLeg = 0.;
4079 bool validActive = false;
4080 if (pr->IsActive() &&
4081 pr->pRoutePointList->GetFirst()->GetData()->m_bIsActive)
4082 validActive = true;
4083
4084 if (segShow_point_a != pr->pRoutePointList->GetFirst()->GetData()) {
4085 wxRoutePointListNode *node =
4086 (pr->pRoutePointList)->GetFirst()->GetNext();
4087 RoutePoint *prp;
4088 float dist_to_endleg = 0;
4089 wxString t;
4090
4091 while (node) {
4092 prp = node->GetData();
4093 if (validActive)
4094 shiptoEndLeg += prp->m_seg_len;
4095 else if (prp->m_bIsActive)
4096 validActive = true;
4097 dist_to_endleg += prp->m_seg_len;
4098 if (prp->IsSame(segShow_point_a)) break;
4099 node = node->GetNext();
4100 }
4101 s << _T(" (+") << FormatDistanceAdaptive(dist_to_endleg) << _T(")");
4102 }
4103 // write from ship to end selected leg point data if the route is
4104 // active
4105 if (validActive) {
4106 s << _T("\n") << _("From Ship To") << _T(" ")
4107 << segShow_point_b->GetName() << _T("\n");
4108 shiptoEndLeg +=
4109 g_pRouteMan
4110 ->GetCurrentRngToActivePoint(); // add distance from ship
4111 // to active point
4112 shiptoEndLeg +=
4113 segShow_point_b
4114 ->m_seg_len; // add the lenght of the selected leg
4115 s << FormatDistanceAdaptive(shiptoEndLeg);
4116 // ensure sog/cog are valid and vmg is positive to keep data
4117 // coherent
4118 double vmg = 0.;
4119 if (!std::isnan(gCog) && !std::isnan(gSog))
4120 vmg = gSog *
4121 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4122 PI / 180.);
4123 if (vmg > 0.) {
4124 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4125 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4126 s << _T(" - ")
4127 << wxString(ttg_sec > SECONDS_PER_DAY
4128 ? ttg_span.Format(_("%Dd %H:%M"))
4129 : ttg_span.Format(_("%H:%M")));
4130 wxDateTime dtnow, eta;
4131 eta = dtnow.SetToCurrent().Add(ttg_span);
4132 s << _T(" - ") << eta.Format(_T("%b")).Mid(0, 4)
4133 << eta.Format(_T(" %d %H:%M"));
4134 } else
4135 s << _T(" ---- ----");
4136 }
4137 m_pRouteRolloverWin->SetString(s);
4138
4139 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4140 LEG_ROLLOVER, win_size);
4141 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4142 m_pRouteRolloverWin->IsActive(true);
4143 b_need_refresh = true;
4144 showRouteRollover = true;
4145 break;
4146 }
4147 } else
4148 node = node->GetNext();
4149 }
4150 } else {
4151 // Is the cursor still in select radius, and not timed out?
4152 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4153 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4154 m_pRolloverRouteSeg))
4155 showRouteRollover = false;
4156 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4157 showRouteRollover = false;
4158 else
4159 showRouteRollover = true;
4160 }
4161
4162 // If currently creating a route, do not show this rollover window
4163 if (m_routeState) showRouteRollover = false;
4164
4165 // Similar for AIS target rollover window
4166 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4167 showRouteRollover = false;
4168
4169 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4170 !showRouteRollover) {
4171 m_pRouteRolloverWin->IsActive(false);
4172 m_pRolloverRouteSeg = NULL;
4173 m_pRouteRolloverWin->Destroy();
4174 m_pRouteRolloverWin = NULL;
4175 b_need_refresh = true;
4176 } else if (m_pRouteRolloverWin && showRouteRollover) {
4177 m_pRouteRolloverWin->IsActive(true);
4178 b_need_refresh = true;
4179 }
4180
4181 // Now the Track info rollover
4182 // Show the track segment info
4183 bool showTrackRollover = false;
4184
4185 if (NULL == m_pRolloverTrackSeg) {
4186 // Get a list of all selectable sgements, and search for the first
4187 // visible segment as the rollover target.
4188
4189 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4190 SelectableItemList SelList = pSelect->FindSelectionList(
4191 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4192 wxSelectableItemListNode *node = SelList.GetFirst();
4193 while (node) {
4194 SelectItem *pFindSel = node->GetData();
4195
4196 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4197
4198 if (pt && pt->IsVisible()) {
4199 m_pRolloverTrackSeg = pFindSel;
4200 showTrackRollover = true;
4201
4202 if (NULL == m_pTrackRolloverWin) {
4203 m_pTrackRolloverWin = new RolloverWin(this, 10);
4204 m_pTrackRolloverWin->IsActive(false);
4205 }
4206
4207 if (!m_pTrackRolloverWin->IsActive()) {
4208 wxString s;
4209 TrackPoint *segShow_point_a =
4210 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4211 TrackPoint *segShow_point_b =
4212 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4213
4214 double brg, dist;
4215 DistanceBearingMercator(
4216 segShow_point_b->m_lat, segShow_point_b->m_lon,
4217 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4218
4219 if (!pt->m_bIsInLayer)
4220 s.Append(_("Track") + _T(": "));
4221 else
4222 s.Append(_("Layer Track: "));
4223
4224 if (pt->GetName().IsEmpty())
4225 s.Append(_("(unnamed)"));
4226 else
4227 s.Append(pt->GetName());
4228 double tlenght = pt->Length();
4229 s << _T("\n") << _("Total Track: ")
4230 << FormatDistanceAdaptive(tlenght);
4231 if (pt->GetLastPoint()->GetTimeString() &&
4232 pt->GetPoint(0)->GetTimeString()) {
4233 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4234 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4235 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4236 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4237 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4238 s << wxString::Format(_T(" %.1f "), (float)(tlenght / htime))
4239 << getUsrSpeedUnit();
4240 s << wxString(htime > 24. ? ttime.Format(_T(" %Dd %H:%M"))
4241 : ttime.Format(_T(" %H:%M")));
4242 }
4243 }
4244
4245 if (g_bShowTrackPointTime && strlen(segShow_point_b->GetTimeString()))
4246 s << _T("\n") << _("Segment Created: ")
4247 << segShow_point_b->GetTimeString();
4248
4249 s << _T("\n");
4250 if (g_bShowTrue)
4251 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4252 0x00B0);
4253
4254 if (g_bShowMag) {
4255 double latAverage =
4256 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4257 double lonAverage =
4258 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4259 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4260
4261 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4262 0x00B0);
4263 }
4264
4265 s << FormatDistanceAdaptive(dist);
4266
4267 if (segShow_point_a->GetTimeString() &&
4268 segShow_point_b->GetTimeString()) {
4269 wxDateTime apoint = segShow_point_a->GetCreateTime();
4270 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4271 if (apoint.IsValid() && bpoint.IsValid()) {
4272 double segmentSpeed = toUsrSpeed(
4273 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4274 s << wxString::Format(_T(" %.1f "), (float)segmentSpeed)
4275 << getUsrSpeedUnit();
4276 }
4277 }
4278
4279 m_pTrackRolloverWin->SetString(s);
4280
4281 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4282 LEG_ROLLOVER, win_size);
4283 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4284 m_pTrackRolloverWin->IsActive(true);
4285 b_need_refresh = true;
4286 showTrackRollover = true;
4287 break;
4288 }
4289 } else
4290 node = node->GetNext();
4291 }
4292 } else {
4293 // Is the cursor still in select radius, and not timed out?
4294 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4295 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4296 m_pRolloverTrackSeg))
4297 showTrackRollover = false;
4298 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4299 showTrackRollover = false;
4300 else
4301 showTrackRollover = true;
4302 }
4303
4304 // Similar for AIS target rollover window
4305 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4306 showTrackRollover = false;
4307
4308 // Similar for route rollover window
4309 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4310 showTrackRollover = false;
4311
4312 // TODO We onlt show tracks on primary canvas....
4313 // if(!IsPrimaryCanvas())
4314 // showTrackRollover = false;
4315
4316 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4317 !showTrackRollover) {
4318 m_pTrackRolloverWin->IsActive(false);
4319 m_pRolloverTrackSeg = NULL;
4320 m_pTrackRolloverWin->Destroy();
4321 m_pTrackRolloverWin = NULL;
4322 b_need_refresh = true;
4323 } else if (m_pTrackRolloverWin && showTrackRollover) {
4324 m_pTrackRolloverWin->IsActive(true);
4325 b_need_refresh = true;
4326 }
4327
4328 if (b_need_refresh) Refresh();
4329}
4330
4331void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4332 if ((GetShowENCLights() || m_bsectors_shown) &&
4333 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4334 extendedSectorLegs)) {
4335 if (!m_bsectors_shown) {
4336 ReloadVP(false);
4337 m_bsectors_shown = true;
4338 }
4339 } else {
4340 if (m_bsectors_shown) {
4341 ReloadVP(false);
4342 m_bsectors_shown = false;
4343 }
4344 }
4345
4346// This is here because GTK status window update is expensive..
4347// cairo using pango rebuilds the font every time so is very
4348// inefficient
4349// Anyway, only update the status bar when this timer expires
4350#if defined(__WXGTK__) || defined(__WXQT__)
4351 {
4352 // Check the absolute range of the cursor position
4353 // There could be a window wherein the chart geoereferencing is not
4354 // valid....
4355 double cursor_lat, cursor_lon;
4356 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4357
4358 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4359 while (cursor_lon < -180.) cursor_lon += 360.;
4360
4361 while (cursor_lon > 180.) cursor_lon -= 360.;
4362
4363 SetCursorStatus(cursor_lat, cursor_lon);
4364 }
4365 }
4366#endif
4367}
4368
4369void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4370 if (!parent_frame->m_pStatusBar) return;
4371
4372 wxString s1;
4373 s1 += _T(" ");
4374 s1 += toSDMM(1, cursor_lat);
4375 s1 += _T(" ");
4376 s1 += toSDMM(2, cursor_lon);
4377
4378 if (STAT_FIELD_CURSOR_LL >= 0)
4379 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4380
4381 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4382
4383 double brg, dist;
4384 wxString sm;
4385 wxString st;
4386 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4387 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4388 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4389
4390 wxString s = st + sm;
4391 s << FormatDistanceAdaptive(dist);
4392
4393 // CUSTOMIZATION - LIVE ETA OPTION
4394 // -------------------------------------------------------
4395 // Calculate an "live" ETA based on route starting from the current
4396 // position of the boat and goes to the cursor of the mouse.
4397 // In any case, an standard ETA will be calculated with a default speed
4398 // of the boat to give an estimation of the route (in particular if GPS
4399 // is off).
4400
4401 // Display only if option "live ETA" is selected in Settings > Display >
4402 // General.
4403 if (g_bShowLiveETA) {
4404 float realTimeETA;
4405 float boatSpeed;
4406 float boatSpeedDefault = g_defaultBoatSpeed;
4407
4408 // Calculate Estimate Time to Arrival (ETA) in minutes
4409 // Check before is value not closed to zero (it will make an very big
4410 // number...)
4411 if (!std::isnan(gSog)) {
4412 boatSpeed = gSog;
4413 if (boatSpeed < 0.5) {
4414 realTimeETA = 0;
4415 } else {
4416 realTimeETA = dist / boatSpeed * 60;
4417 }
4418 } else {
4419 realTimeETA = 0;
4420 }
4421
4422 // Add space after distance display
4423 s << " ";
4424 // Display ETA
4425 s << minutesToHoursDays(realTimeETA);
4426
4427 // In any case, display also an ETA with default speed at 6knts
4428
4429 s << " [@";
4430 s << wxString::Format(_T("%d"), (int)toUsrSpeed(boatSpeedDefault, -1));
4431 s << wxString::Format(_T("%s"), getUsrSpeedUnit(-1));
4432 s << " ";
4433 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4434 s << "]";
4435 }
4436 // END OF - LIVE ETA OPTION
4437
4438 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4439}
4440
4441// CUSTOMIZATION - FORMAT MINUTES
4442// -------------------------------------------------------
4443// New function to format minutes into a more readable format:
4444// * Hours + minutes, or
4445// * Days + hours.
4446wxString minutesToHoursDays(float timeInMinutes) {
4447 wxString s;
4448
4449 if (timeInMinutes == 0) {
4450 s << "--min";
4451 }
4452
4453 // Less than 60min, keep time in minutes
4454 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4455 s << wxString::Format(_T("%d"), (int)timeInMinutes);
4456 s << "min";
4457 }
4458
4459 // Between 1h and less than 24h, display time in hours, minutes
4460 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4461 int hours;
4462 int min;
4463 hours = (int)timeInMinutes / 60;
4464 min = (int)timeInMinutes % 60;
4465
4466 if (min == 0) {
4467 s << wxString::Format(_T("%d"), hours);
4468 s << "h";
4469 } else {
4470 s << wxString::Format(_T("%d"), hours);
4471 s << "h";
4472 s << wxString::Format(_T("%d"), min);
4473 s << "min";
4474 }
4475
4476 }
4477
4478 // More than 24h, display time in days, hours
4479 else if (timeInMinutes > 24 * 60) {
4480 int days;
4481 int hours;
4482 days = (int)(timeInMinutes / 60) / 24;
4483 hours = (int)(timeInMinutes / 60) % 24;
4484
4485 if (hours == 0) {
4486 s << wxString::Format(_T("%d"), days);
4487 s << "d";
4488 } else {
4489 s << wxString::Format(_T("%d"), days);
4490 s << "d";
4491 s << wxString::Format(_T("%d"), hours);
4492 s << "h";
4493 }
4494 }
4495
4496 return s;
4497}
4498
4499// END OF CUSTOMIZATION - FORMAT MINUTES
4500// Thanks open source code ;-)
4501// -------------------------------------------------------
4502
4503void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4504 double clat, clon;
4505 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4506 *lat = clat;
4507 *lon = clon;
4508}
4509
4510void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4511 wxPoint2DDouble *r) {
4512 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4513}
4514
4516 double rlon, wxPoint2DDouble *r) {
4517 // If the Current Chart is a raster chart, and the
4518 // requested lat/long is within the boundaries of the chart,
4519 // and the VP is not rotated,
4520 // then use the embedded BSB chart georeferencing algorithm
4521 // for greater accuracy
4522 // Additionally, use chart embedded georef if the projection is TMERC
4523 // i.e. NOT MERCATOR and NOT POLYCONIC
4524
4525 // If for some reason the chart rejects the request by returning an error,
4526 // then fall back to Viewport Projection estimate from canvas parameters
4527 if (!g_bopengl && m_singleChart &&
4528 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4529 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4530 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4531 (m_singleChart->GetChartProjectionType() !=
4532 PROJECTION_TRANSVERSE_MERCATOR) &&
4533 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4534 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4535 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4536 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4537 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4538 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4539 // Cur_BSB_Ch->GetCOVRTablenPoints
4540 // ( 0 ), rlon,
4541 // rlat );
4542 // bInside = true;
4543 // if ( bInside )
4544 if (Cur_BSB_Ch) {
4545 // This is a Raster chart....
4546 // If the VP is changing, the raster chart parameters may not yet be
4547 // setup So do that before accessing the chart's embedded
4548 // georeferencing
4549 Cur_BSB_Ch->SetVPRasterParms(vp);
4550 double rpixxd, rpixyd;
4551 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4552 r->m_x = rpixxd;
4553 r->m_y = rpixyd;
4554 return;
4555 }
4556 }
4557 }
4558
4559 // if needed, use the VPoint scaling estimator,
4560 *r = vp.GetDoublePixFromLL(rlat, rlon);
4561}
4562
4563// This routine might be deleted and all of the rendering improved
4564// to have floating point accuracy
4565bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4566 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4567}
4568
4569bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4570 wxPoint *r) {
4571 wxPoint2DDouble p;
4572 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4573
4574 // some projections give nan values when invisible values (other side of
4575 // world) are requested we should stop using integer coordinates or return
4576 // false here (and test it everywhere)
4577 if (std::isnan(p.m_x)) {
4578 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4579 return false;
4580 }
4581
4582 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4583 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4584 else
4585 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4586
4587 return true;
4588}
4589
4590void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4591 double &lon) {
4592 // If the Current Chart is a raster chart, and the
4593 // requested x,y is within the boundaries of the chart,
4594 // and the VP is not rotated,
4595 // then use the embedded BSB chart georeferencing algorithm
4596 // for greater accuracy
4597 // Additionally, use chart embedded georef if the projection is TMERC
4598 // i.e. NOT MERCATOR and NOT POLYCONIC
4599
4600 // If for some reason the chart rejects the request by returning an error,
4601 // then fall back to Viewport Projection estimate from canvas parameters
4602 bool bUseVP = true;
4603
4604 if (!g_bopengl && m_singleChart &&
4605 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4606 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4607 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4608 (m_singleChart->GetChartProjectionType() !=
4609 PROJECTION_TRANSVERSE_MERCATOR) &&
4610 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4611 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4612 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4613 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4614
4615 // TODO maybe need iterative process to validate bInside
4616 // first pass is mercator, then check chart boundaries
4617
4618 if (Cur_BSB_Ch) {
4619 // This is a Raster chart....
4620 // If the VP is changing, the raster chart parameters may not yet be
4621 // setup So do that before accessing the chart's embedded
4622 // georeferencing
4623 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4624
4625 double slat, slon;
4626 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4627 lat = slat;
4628
4629 if (slon < -180.)
4630 slon += 360.;
4631 else if (slon > 180.)
4632 slon -= 360.;
4633
4634 lon = slon;
4635 bUseVP = false;
4636 }
4637 }
4638 }
4639
4640 // if needed, use the VPoint scaling estimator
4641 if (bUseVP) {
4642 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4643 }
4644}
4645
4647 DoZoomCanvas(factor, false);
4648 extendedSectorLegs.clear();
4649}
4650
4651void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4652 bool stoptimer) {
4653 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4654
4655 if (g_bsmoothpanzoom) {
4656 if (StartTimedMovement(stoptimer)) {
4657 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4658 m_zoom_factor = factor;
4659 }
4660
4661 m_zoom_target = VPoint.chart_scale / factor;
4662 } else {
4663 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4664
4665 DoZoomCanvas(factor, can_zoom_to_cursor);
4666 }
4667
4668 extendedSectorLegs.clear();
4669}
4670
4671void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4672 // possible on startup
4673 if (!ChartData) return;
4674 if (!m_pCurrentStack) return;
4675
4676 /* TODO: queue the quilted loading code to a background thread
4677 so yield is never called from here, and also rendering is not delayed */
4678
4679 // Cannot allow Yield() re-entrancy here
4680 if (m_bzooming) return;
4681 m_bzooming = true;
4682
4683 double old_ppm = GetVP().view_scale_ppm;
4684
4685 // Capture current cursor position for zoom to cursor
4686 double zlat = m_cursor_lat;
4687 double zlon = m_cursor_lon;
4688
4689 double proposed_scale_onscreen =
4690 GetVP().chart_scale /
4691 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4692 bool b_do_zoom = false;
4693
4694 if (factor > 1) {
4695 b_do_zoom = true;
4696
4697 // double zoom_factor = factor;
4698
4699 ChartBase *pc = NULL;
4700
4701 if (!VPoint.b_quilt) {
4702 pc = m_singleChart;
4703 } else {
4704 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4705 if (new_db_index >= 0)
4706 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4707 else { // for whatever reason, no reference chart is known
4708 // Choose the smallest scale chart on the current stack
4709 // and then adjust for scale range
4710 int current_ref_stack_index = -1;
4711 if (m_pCurrentStack->nEntry) {
4712 int trial_index =
4713 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4714 m_pQuilt->SetReferenceChart(trial_index);
4715 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4716 if (new_db_index >= 0)
4717 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4718 }
4719 }
4720
4721 if (m_pCurrentStack)
4722 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4723 new_db_index); // highlite the correct bar entry
4724 }
4725
4726 if (pc) {
4727 // double target_scale_ppm = GetVPScale() * zoom_factor;
4728 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4729 // target_scale_ppm;
4730
4731 // Query the chart to determine the appropriate zoom range
4732 double min_allowed_scale =
4733 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4734
4735 if (proposed_scale_onscreen < min_allowed_scale) {
4736 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4737 m_zoom_factor = 1; /* stop zooming */
4738 b_do_zoom = false;
4739 } else
4740 proposed_scale_onscreen = min_allowed_scale;
4741 }
4742
4743 } else {
4744 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4745 }
4746
4747 } else if (factor < 1) {
4748 b_do_zoom = true;
4749
4750 ChartBase *pc = NULL;
4751
4752 bool b_smallest = false;
4753
4754 if (!VPoint.b_quilt) { // not quilted
4755 pc = m_singleChart;
4756
4757 if (pc) {
4758 // If m_singleChart is not on the screen, unbound the zoomout
4759 LLBBox viewbox = VPoint.GetBBox();
4760 // BoundingBox chart_box;
4761 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4762 double max_allowed_scale;
4763
4764 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4765
4766 // We can allow essentially unbounded zoomout in single chart mode
4767 // if( ChartData->GetDBBoundingBox( current_index,
4768 // &chart_box ) &&
4769 // !viewbox.IntersectOut( chart_box ) )
4770 // // Clamp the minimum scale zoom-out to the value
4771 // specified by the chart max_allowed_scale =
4772 // wxMin(max_allowed_scale, 4.0 *
4773 // pc->GetNormalScaleMax(
4774 // GetCanvasScaleFactor(),
4775 // GetCanvasWidth() ) );
4776 if (proposed_scale_onscreen > max_allowed_scale) {
4777 m_zoom_factor = 1; /* stop zooming */
4778 proposed_scale_onscreen = max_allowed_scale;
4779 }
4780 }
4781
4782 } else {
4783 int new_db_index = m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4784 if (new_db_index >= 0)
4785 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4786
4787 if (m_pCurrentStack)
4788 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4789 new_db_index); // highlite the correct bar entry
4790
4791 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4792
4793 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4794 proposed_scale_onscreen =
4795 wxMin(proposed_scale_onscreen,
4796 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4797 }
4798
4799 // set a minimum scale
4800 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4801 m_absolute_min_scale_ppm)
4802 b_do_zoom = false;
4803 }
4804
4805 double new_scale =
4806 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4807
4808 if (b_do_zoom) {
4809 // Disable ZTC if lookahead is ON, and currently b_follow is active
4810 bool b_allow_ztc = true;
4811 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4812 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4813 if (m_bLookAhead) {
4814 double brg, distance;
4815 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4816 &distance);
4817 dir_to_shift = brg;
4818 meters_to_shift = distance * 1852;
4819 }
4820 // Arrange to combine the zoom and pan into one operation for smoother
4821 // appearance
4822 SetVPScale(new_scale, false); // adjust, but deferred refresh
4823 wxPoint r;
4824 GetCanvasPointPix(zlat, zlon, &r);
4825 // this will emit the Refresh()
4826 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4827 } else {
4828 SetVPScale(new_scale);
4829 if (m_bFollow) DoCanvasUpdate();
4830 }
4831 }
4832
4833 m_bzooming = false;
4834}
4835int rot;
4836void ChartCanvas::RotateCanvas(double dir) {
4837 // SetUpMode(NORTH_UP_MODE);
4838
4839 if (g_bsmoothpanzoom) {
4840 if (StartTimedMovement()) {
4841 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4842 m_rotation_speed = dir * 60;
4843 }
4844 } else {
4845 double speed = dir * 10;
4846 if (m_modkeys == wxMOD_ALT) speed /= 20;
4847 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4848 }
4849}
4850
4851void ChartCanvas::DoRotateCanvas(double rotation) {
4852 while (rotation < 0) rotation += 2 * PI;
4853 while (rotation > 2 * PI) rotation -= 2 * PI;
4854
4855 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4856
4857 SetVPRotation(rotation);
4858 parent_frame->UpdateRotationState(VPoint.rotation);
4859}
4860
4861void ChartCanvas::DoTiltCanvas(double tilt) {
4862 while (tilt < 0) tilt = 0;
4863 while (tilt > .95) tilt = .95;
4864
4865 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4866
4867 VPoint.tilt = tilt;
4868 Refresh(false);
4869}
4870
4871void ChartCanvas::TogglebFollow(void) {
4872 if (!m_bFollow)
4873 SetbFollow();
4874 else
4875 ClearbFollow();
4876}
4877
4878void ChartCanvas::ClearbFollow(void) {
4879 m_bFollow = false; // update the follow flag
4880
4881 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4882
4883 UpdateFollowButtonState();
4884
4885 DoCanvasUpdate();
4886 ReloadVP();
4887 parent_frame->SetChartUpdatePeriod();
4888}
4889
4890void ChartCanvas::SetbFollow(void) {
4891 // Is the OWNSHIP on-screen?
4892 // If not, then reset the OWNSHIP offset to 0 (center screen)
4893 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4894 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4895 m_OSoffsetx = 0;
4896 m_OSoffsety = 0;
4897 }
4898
4899 // Apply the present b_follow offset values to ship position
4900 wxPoint2DDouble p;
4901 GetDoubleCanvasPointPix(gLat, gLon, &p);
4902 p.m_x += m_OSoffsetx;
4903 p.m_y -= m_OSoffsety;
4904
4905 // compute the target center screen lat/lon
4906 double dlat, dlon;
4907 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4908
4909 JumpToPosition(dlat, dlon, GetVPScale());
4910 m_bFollow = true;
4911
4912 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4913 UpdateFollowButtonState();
4914
4915 if (!g_bSmoothRecenter) {
4916 DoCanvasUpdate();
4917 ReloadVP();
4918 }
4919 parent_frame->SetChartUpdatePeriod();
4920}
4921
4922void ChartCanvas::UpdateFollowButtonState(void) {
4923 if (m_muiBar) {
4924 if (!m_bFollow)
4925 m_muiBar->SetFollowButtonState(0);
4926 else {
4927 if (m_bLookAhead)
4928 m_muiBar->SetFollowButtonState(2);
4929 else
4930 m_muiBar->SetFollowButtonState(1);
4931 }
4932 }
4933
4934#ifdef __ANDROID__
4935 if (!m_bFollow)
4936 androidSetFollowTool(0);
4937 else {
4938 if (m_bLookAhead)
4939 androidSetFollowTool(2);
4940 else
4941 androidSetFollowTool(1);
4942 }
4943#endif
4944}
4945
4946void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4947 if (g_bSmoothRecenter && !m_routeState) {
4948 if (StartSmoothJump(lat, lon, scale_ppm))
4949 return;
4950 else {
4951 // move closer to the target destination, and try again
4952 double gcDist, gcBearingEnd;
4953 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4954 &gcBearingEnd);
4955 gcBearingEnd += 180;
4956 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4957 GetCanvasWidth() / GetVPScale(); // meters
4958 double lon_offset =
4959 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4960 double new_lat = lat + (lat_offset / (1852 * 60));
4961 double new_lon = lon + (lon_offset / (1852 * 60));
4962 SetViewPoint(new_lat, new_lon);
4963 ReloadVP();
4964 StartSmoothJump(lat, lon, scale_ppm);
4965 return;
4966 }
4967 }
4968
4969 if (lon > 180.0) lon -= 360.0;
4970 m_vLat = lat;
4971 m_vLon = lon;
4972 StopMovement();
4973 m_bFollow = false;
4974
4975 if (!GetQuiltMode()) {
4976 double skew = 0;
4977 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4978 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4979 } else {
4980 if (scale_ppm != GetVPScale()) {
4981 // XXX should be done in SetViewPoint
4982 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4983 AdjustQuiltRefChart();
4984 }
4985 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4986 }
4987
4988 ReloadVP();
4989
4990 UpdateFollowButtonState();
4991
4992 // TODO
4993 // if( g_pi_manager ) {
4994 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4995 // }
4996}
4997
4998bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
4999 // Check distance to jump, in pixels at current chart scale
5000 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5001 // width.
5002 double gcDist;
5003 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5004 double distance_pixels = gcDist * GetVPScale();
5005 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5006 // Jump is too far, try again
5007 return false;
5008 }
5009
5010 // Save where we're coming from
5011 m_startLat = m_vLat;
5012 m_startLon = m_vLon;
5013 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5014
5015 // Save where we want to end up
5016 m_endLat = lat;
5017 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5018 m_endScale = scale_ppm;
5019
5020 // Setup timing
5021 m_animationDuration = 600; // ms
5022 m_animationStart = wxGetLocalTimeMillis();
5023
5024 // Stop any previous movement, ensure no conflicts
5025 StopMovement();
5026 m_bFollow = false;
5027
5028 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5029 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5030 m_animationActive = true;
5031
5032 return true;
5033}
5034
5035void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5036 // Calculate time fraction from 0..1
5037 wxLongLong now = wxGetLocalTimeMillis();
5038 double elapsed = (now - m_animationStart).ToDouble();
5039 double t = elapsed / m_animationDuration.ToDouble();
5040 if (t > 1.0) t = 1.0;
5041
5042 // Ease function for smoother movement
5043 double e = easeOutCubic(t);
5044
5045 // Interpolate lat/lon/scale
5046 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5047 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5048 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5049
5050 // Update viewpoint
5051 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5052 // portion)
5053 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5054 ReloadVP();
5055
5056 // If we reached the end, stop the timer and finalize
5057 if (t >= 1.0) {
5058 m_easeTimer.Stop();
5059 m_animationActive = false;
5060 UpdateFollowButtonState();
5061 DoCanvasUpdate();
5062 ReloadVP();
5063 }
5064}
5065
5066bool ChartCanvas::PanCanvas(double dx, double dy) {
5067 if (!ChartData) return false;
5068
5069 if (g_btouch) {
5070 // Stop bfollow state, without a refresh
5071 m_bFollow = false; // update the follow flag
5072 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
5073 UpdateFollowButtonState();
5074 // Clear the bfollow offset
5075 m_OSoffsetx = 0;
5076 m_OSoffsety = 0;
5077 }
5078
5079 extendedSectorLegs.clear();
5080
5081 // double clat = VPoint.clat, clon = VPoint.clon;
5082 double dlat, dlon;
5083 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5084
5085 int iters = 0;
5086 for (;;) {
5087 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5088
5089 if (iters++ > 5) return false;
5090 if (!std::isnan(dlat)) break;
5091
5092 dx *= .5, dy *= .5;
5093 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5094 }
5095
5096 // avoid overshooting the poles
5097 if (dlat > 90)
5098 dlat = 90;
5099 else if (dlat < -90)
5100 dlat = -90;
5101
5102 if (dlon > 360.) dlon -= 360.;
5103 if (dlon < -360.) dlon += 360.;
5104
5105 // This should not really be necessary, but round-trip georef on some
5106 // charts is not perfect, So we can get creep on repeated unidimensional
5107 // pans, and corrupt chart cacheing.......
5108
5109 // But this only works on north-up projections
5110 // TODO: can we remove this now?
5111 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5112 // .001 ) ) {
5113 //
5114 // if( dx == 0 ) dlon = clon;
5115 // if( dy == 0 ) dlat = clat;
5116 // }
5117
5118 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5119
5120 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5121
5122 if (VPoint.b_quilt) {
5123 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5124 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5125 // Tweak the scale slightly for a new ref chart
5126 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5127 if (pc) {
5128 double tweak_scale_ppm =
5129 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5130 SetVPScale(tweak_scale_ppm);
5131 }
5132 }
5133
5134 if (new_ref_dbIndex == -1) {
5135#pragma GCC diagnostic push
5136#pragma GCC diagnostic ignored "-Warray-bounds"
5137 // The compiler sees a -1 index being used. Does not happen, though.
5138
5139 // for whatever reason, no reference chart is known
5140 // Probably panned out of the coverage region
5141 // If any charts are anywhere on-screen, choose the smallest
5142 // scale chart on the screen to be a new reference chart.
5143 int trial_index = -1;
5144 if (m_pCurrentStack->nEntry) {
5145 int trial_index =
5146 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5147 }
5148
5149 if (trial_index < 0) {
5150 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5151 if (full_screen_array.size())
5152 trial_index = full_screen_array[full_screen_array.size() - 1];
5153 }
5154
5155 if (trial_index >= 0) {
5156 m_pQuilt->SetReferenceChart(trial_index);
5157 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5158 VPoint.rotation);
5159 ReloadVP();
5160 }
5161#pragma GCC diagnostic pop
5162 }
5163 }
5164
5165 // Turn off bFollow only if the ownship has left the screen
5166 if (m_bFollow) {
5167 double offx, offy;
5168 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5169
5170 double offset_angle = atan2(offy, offx);
5171 double offset_distance = sqrt((offy * offy) + (offx * offx));
5172 double chart_angle = GetVPRotation();
5173 double target_angle = chart_angle - offset_angle;
5174 double d_east_mod = offset_distance * cos(target_angle);
5175 double d_north_mod = offset_distance * sin(target_angle);
5176
5177 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5178 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5179
5180 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5181 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5182 m_bFollow = false; // update the follow flag
5183 UpdateFollowButtonState();
5184 }
5185 }
5186
5187 Refresh(false);
5188
5189 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5190
5191 return true;
5192}
5193
5194void ChartCanvas::ReloadVP(bool b_adjust) {
5195 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5196
5197 LoadVP(VPoint, b_adjust);
5198}
5199
5200void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5201#ifdef ocpnUSE_GL
5202 if (g_bopengl && m_glcc) {
5203 m_glcc->Invalidate();
5204 if (m_glcc->GetSize() != GetSize()) {
5205 m_glcc->SetSize(GetSize());
5206 }
5207 } else
5208#endif
5209 {
5210 m_cache_vp.Invalidate();
5211 m_bm_cache_vp.Invalidate();
5212 }
5213
5214 VPoint.Invalidate();
5215
5216 if (m_pQuilt) m_pQuilt->Invalidate();
5217
5218 // Make sure that the Selected Group is sensible...
5219 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5220 // m_groupIndex = 0;
5221 // if( !CheckGroup( m_groupIndex ) )
5222 // m_groupIndex = 0;
5223
5224 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5225 vp.m_projection_type, b_adjust);
5226}
5227
5228void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5229 m_pQuilt->SetReferenceChart(dbIndex);
5230 VPoint.Invalidate();
5231 m_pQuilt->Invalidate();
5232}
5233
5234double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5235 if (m_pQuilt)
5236 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5237 else
5238 return vp.view_scale_ppm;
5239}
5240
5241// Verify and adjust the current reference chart,
5242// so that it will not lead to excessive overzoom or underzoom onscreen
5243int ChartCanvas::AdjustQuiltRefChart() {
5244 int ret = -1;
5245 if (m_pQuilt) {
5246 wxASSERT(ChartData);
5247 ChartBase *pc =
5248 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5249 if (pc) {
5250 double min_ref_scale =
5251 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5252 double max_ref_scale =
5253 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5254
5255 if (VPoint.chart_scale < min_ref_scale) {
5256 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5257 } else if (VPoint.chart_scale > max_ref_scale) {
5258 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5259 } else {
5260 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5261
5262 int ref_family = pc->GetChartFamily();
5263
5264 if (!brender_ok) {
5265 unsigned int target_stack_index = 0;
5266 int target_stack_index_check =
5267 m_pQuilt->GetExtendedStackIndexArray()
5268 [m_pQuilt->GetRefChartdbIndex()]; // Lookup
5269
5270 if (wxNOT_FOUND != target_stack_index_check)
5271 target_stack_index = target_stack_index_check;
5272
5273 int extended_array_count =
5274 m_pQuilt->GetExtendedStackIndexArray().size();
5275 while ((!brender_ok) &&
5276 ((int)target_stack_index < (extended_array_count - 1))) {
5277 target_stack_index++;
5278 int test_db_index =
5279 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5280
5281 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5282 IsChartQuiltableRef(test_db_index)) {
5283 // open the target, and check the min_scale
5284 ChartBase *ptest_chart =
5285 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5286 if (ptest_chart) {
5287 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5288 }
5289 }
5290 }
5291
5292 if (brender_ok) { // found a better reference chart
5293 int new_db_index =
5294 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5295 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5296 IsChartQuiltableRef(new_db_index)) {
5297 m_pQuilt->SetReferenceChart(new_db_index);
5298 ret = new_db_index;
5299 } else
5300 ret = m_pQuilt->GetRefChartdbIndex();
5301 } else
5302 ret = m_pQuilt->GetRefChartdbIndex();
5303
5304 } else
5305 ret = m_pQuilt->GetRefChartdbIndex();
5306 }
5307 } else
5308 ret = -1;
5309 }
5310
5311 return ret;
5312}
5313
5314void ChartCanvas::UpdateCanvasOnGroupChange(void) {
5315 delete m_pCurrentStack;
5316 m_pCurrentStack = new ChartStack;
5317 wxASSERT(ChartData);
5318 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5319 m_groupIndex);
5320
5321 if (m_pQuilt) {
5322 m_pQuilt->Compose(VPoint);
5323 SetFocus();
5324 }
5325}
5326
5327bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5328 double latNE, double lonNE) {
5329 // Center Point
5330 double latc = (latSW + latNE) / 2.0;
5331 double lonc = (lonSW + lonNE) / 2.0;
5332
5333 // Get scale in ppm (latitude)
5334 double ne_easting, ne_northing;
5335 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5336
5337 double sw_easting, sw_northing;
5338 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5339
5340 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5341
5342 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5343}
5344
5345bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5346 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5347 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5348}
5349
5350bool ChartCanvas::SetVPProjection(int projection) {
5351 if (!g_bopengl) // alternative projections require opengl
5352 return false;
5353
5354 // the view scale varies depending on geographic location and projection
5355 // rescale to keep the relative scale on the screen the same
5356 double prev_true_scale_ppm = m_true_scale_ppm;
5357 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5358 VPoint.skew, VPoint.rotation, projection) &&
5359 SetVPScale(wxMax(
5360 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5361 m_absolute_min_scale_ppm));
5362}
5363
5364bool ChartCanvas::SetViewPoint(double lat, double lon) {
5365 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5366 VPoint.rotation);
5367}
5368
5369bool ChartCanvas::SetVPRotation(double angle) {
5370 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5371 VPoint.skew, angle);
5372}
5373bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5374 double skew, double rotation, int projection,
5375 bool b_adjust, bool b_refresh) {
5376 bool b_ret = false;
5377 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5378 skew -= 2 * PI;
5379 // Any sensible change?
5380 if (VPoint.IsValid()) {
5381 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5382 (fabs(VPoint.skew - skew) < 1e-9) &&
5383 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5384 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5385 (VPoint.m_projection_type == projection ||
5386 projection == PROJECTION_UNKNOWN))
5387 return false;
5388 }
5389 if (VPoint.m_projection_type != projection)
5390 VPoint.InvalidateTransformCache(); // invalidate
5391
5392 // Take a local copy of the last viewport
5393 ViewPort last_vp = VPoint;
5394
5395 VPoint.skew = skew;
5396 VPoint.clat = lat;
5397 VPoint.clon = lon;
5398 VPoint.rotation = rotation;
5399 VPoint.view_scale_ppm = scale_ppm;
5400 if (projection != PROJECTION_UNKNOWN)
5401 VPoint.SetProjectionType(projection);
5402 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5403 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5404
5405 // don't allow latitude above 88 for mercator (90 is infinity)
5406 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5407 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5408 if (VPoint.clat > 89.5)
5409 VPoint.clat = 89.5;
5410 else if (VPoint.clat < -89.5)
5411 VPoint.clat = -89.5;
5412 }
5413
5414 // don't zoom out too far for transverse mercator polyconic until we resolve
5415 // issues
5416 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5417 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5418 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5419
5420 // SetVPRotation(rotation);
5421
5422 if (!g_bopengl) // tilt is not possible without opengl
5423 VPoint.tilt = 0;
5424
5425 if ((VPoint.pix_width <= 0) ||
5426 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5427 return false;
5428
5429 bool bwasValid = VPoint.IsValid();
5430 VPoint.Validate(); // Mark this ViewPoint as OK
5431
5432 // Has the Viewport scale changed? If so, invalidate the vp
5433 if (last_vp.view_scale_ppm != scale_ppm) {
5434 m_cache_vp.Invalidate();
5435 InvalidateGL();
5436 }
5437
5438 // A preliminary value, may be tweaked below
5439 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5440
5441 // recompute cursor position
5442 // and send to interested plugins if the mouse is actually in this window
5443 int mouseX = mouse_x;
5444 int mouseY = mouse_y;
5445 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5446 (mouseY < VPoint.pix_height)) {
5447 double lat, lon;
5448 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5449 m_cursor_lat = lat;
5450 m_cursor_lon = lon;
5451 SendCursorLatLonToAllPlugIns(lat, lon);
5452 }
5453
5454 if (!VPoint.b_quilt && m_singleChart) {
5455 VPoint.SetBoxes();
5456
5457 // Allow the chart to adjust the new ViewPort for performance optimization
5458 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5459 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5460
5461 // If there is a sensible change in the chart render, refresh the whole
5462 // screen
5463 if ((!m_cache_vp.IsValid()) ||
5464 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5465 Refresh(false);
5466 b_ret = true;
5467 } else {
5468 wxPoint cp_last, cp_this;
5469 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5470 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5471
5472 if (cp_last != cp_this) {
5473 Refresh(false);
5474 b_ret = true;
5475 }
5476 }
5477 // Create the stack
5478 if (m_pCurrentStack) {
5479 assert(ChartData != 0);
5480 int current_db_index;
5481 current_db_index =
5482 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5483
5484 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5485 m_groupIndex);
5486 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5487 }
5488
5489 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5490 }
5491
5492 // Handle the quilted case
5493 if (VPoint.b_quilt) {
5494 if (last_vp.view_scale_ppm != scale_ppm)
5495 m_pQuilt->InvalidateAllQuiltPatchs();
5496
5497 // Create the quilt
5498 if (ChartData /*&& ChartData->IsValid()*/) {
5499 if (!m_pCurrentStack) return false;
5500
5501 int current_db_index;
5502 current_db_index =
5503 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5504
5505 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5506 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5507
5508 // Check to see if the current quilt reference chart is in the new stack
5509 int current_ref_stack_index = -1;
5510 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5511 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5512 current_ref_stack_index = i;
5513 }
5514
5515 if (g_bFullScreenQuilt) {
5516 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5517 }
5518
5519 // We might need a new Reference Chart
5520 bool b_needNewRef = false;
5521
5522 // If the new stack does not contain the current ref chart....
5523 if ((-1 == current_ref_stack_index) &&
5524 (m_pQuilt->GetRefChartdbIndex() >= 0))
5525 b_needNewRef = true;
5526
5527 // Would the current Ref Chart be excessively underzoomed?
5528 // We need to check this here to be sure, since we cannot know where the
5529 // reference chart was assigned. For instance, the reference chart may
5530 // have been selected from the config file, or from a long jump with a
5531 // chart family switch implicit. Anyway, we check to be sure....
5532 bool renderable = true;
5533 ChartBase *referenceChart =
5534 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5535 if (referenceChart) {
5536 double chartMaxScale = referenceChart->GetNormalScaleMax(
5537 GetCanvasScaleFactor(), GetCanvasWidth());
5538 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5539 }
5540 if (!renderable) b_needNewRef = true;
5541
5542 // Need new refchart?
5543 if (b_needNewRef) {
5544 const ChartTableEntry &cte_ref =
5545 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5546 int target_scale = cte_ref.GetScale();
5547 int target_type = cte_ref.GetChartType();
5548 int candidate_stack_index;
5549
5550 // reset the ref chart in a way that does not lead to excessive
5551 // underzoom, for performance reasons Try to find a chart that is the
5552 // same type, and has a scale of just smaller than the current ref
5553 // chart
5554
5555 candidate_stack_index = 0;
5556 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5557 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5558 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5559 int candidate_scale = cte_candidate.GetScale();
5560 int candidate_type = cte_candidate.GetChartType();
5561
5562 if ((candidate_scale >= target_scale) &&
5563 (candidate_type == target_type)) {
5564 bool renderable = true;
5565 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5566 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5567 if (tentative_referenceChart) {
5568 double chartMaxScale =
5569 tentative_referenceChart->GetNormalScaleMax(
5570 GetCanvasScaleFactor(), GetCanvasWidth());
5571 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5572 }
5573
5574 if (renderable) break;
5575 }
5576
5577 candidate_stack_index++;
5578 }
5579
5580 // If that did not work, look for a chart of just larger scale and
5581 // same type
5582 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5583 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5584 while (candidate_stack_index >= 0) {
5585 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5586 if (idx >= 0) {
5587 const ChartTableEntry &cte_candidate =
5588 ChartData->GetChartTableEntry(idx);
5589 int candidate_scale = cte_candidate.GetScale();
5590 int candidate_type = cte_candidate.GetChartType();
5591
5592 if ((candidate_scale <= target_scale) &&
5593 (candidate_type == target_type))
5594 break;
5595 }
5596 candidate_stack_index--;
5597 }
5598 }
5599
5600 // and if that did not work, chose stack entry 0
5601 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5602 (candidate_stack_index < 0))
5603 candidate_stack_index = 0;
5604
5605 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5606
5607 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5608 }
5609
5610 if (!g_bopengl) {
5611 // Preset the VPoint projection type to match what the quilt projection
5612 // type will be
5613 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5614
5615 // Always keep the default Mercator projection if the reference chart is
5616 // not in the PatchList or the scale is too small for it to render.
5617
5618 bool renderable = true;
5619 ChartBase *referenceChart =
5620 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5621 if (referenceChart) {
5622 double chartMaxScale = referenceChart->GetNormalScaleMax(
5623 GetCanvasScaleFactor(), GetCanvasWidth());
5624 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5625 proj = ChartData->GetDBChartProj(ref_db_index);
5626 } else
5627 proj = PROJECTION_MERCATOR;
5628
5629 VPoint.b_MercatorProjectionOverride =
5630 (m_pQuilt->GetnCharts() == 0 || !renderable);
5631
5632 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5633
5634 VPoint.SetProjectionType(proj);
5635 }
5636
5637 VPoint.SetBoxes();
5638
5639 // If this quilt will be a perceptible delta from the existing quilt,
5640 // then refresh the entire screen
5641 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5642 // Allow the quilt to adjust the new ViewPort for performance
5643 // optimization This will normally be only a fractional (i.e.
5644 // sub-pixel) adjustment...
5645 if (b_adjust) {
5646 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5647 }
5648
5649 // ChartData->ClearCacheInUseFlags();
5650 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5651
5652 // wxStopWatch sw;
5653
5654#ifdef __ANDROID__
5655 // This is an optimization for panning on touch screen systems.
5656 // The quilt composition is deferred until the OnPaint() message gets
5657 // finally removed and processed from the message queue.
5658 // Takes advantage of the fact that touch-screen pan gestures are
5659 // usually short in distance,
5660 // so not requiring a full quilt rebuild until the pan gesture is
5661 // complete.
5662 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5663 // qDebug() << "Force compose";
5664 m_pQuilt->Compose(VPoint);
5665 } else {
5666 m_pQuilt->Invalidate();
5667 }
5668#else
5669 m_pQuilt->Compose(VPoint);
5670#endif
5671
5672 // printf("comp time %ld\n", sw.Time());
5673
5674 // If the extended chart stack has changed, invalidate any cached
5675 // render bitmap
5676 // if(m_pQuilt->GetXStackHash() != hash1) {
5677 // m_bm_cache_vp.Invalidate();
5678 // InvalidateGL();
5679 // }
5680
5681 ChartData->PurgeCacheUnusedCharts(0.7);
5682
5683 if (b_refresh) Refresh(false);
5684
5685 b_ret = true;
5686 }
5687 }
5688
5689 VPoint.skew = 0.; // Quilting supports 0 Skew
5690 } else if (!g_bopengl) {
5691 OcpnProjType projection = PROJECTION_UNKNOWN;
5692 if (m_singleChart) // viewport projection must match chart projection
5693 // without opengl
5694 projection = m_singleChart->GetChartProjectionType();
5695 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5696 VPoint.SetProjectionType(projection);
5697 }
5698
5699 // Has the Viewport projection changed? If so, invalidate the vp
5700 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5701 m_cache_vp.Invalidate();
5702 InvalidateGL();
5703 }
5704
5705 UpdateCanvasControlBar(); // Refresh the Piano
5706
5707 VPoint.chart_scale = 1.0; // fallback default value
5708
5709 /*if (!VPoint.GetBBox().GetValid())*/ VPoint.SetBoxes();
5710
5711 if (VPoint.GetBBox().GetValid()) {
5712 // Update the viewpoint reference scale
5713 if (m_singleChart)
5714 VPoint.ref_scale = m_singleChart->GetNativeScale();
5715 else {
5716#ifdef __ANDROID__
5717 // This is an optimization for panning on touch screen systems.
5718 // See above.
5719 // Quilt might not be fully composed at this point, so for cm93
5720 // the reference scale may not be known.
5721 // In this case, do not update the VP ref_scale.
5722 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5723 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5724 }
5725#else
5726 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5727#endif
5728 }
5729
5730 // Calculate the on-screen displayed actual scale
5731 // by a simple traverse northward from the center point
5732 // of roughly one eighth of the canvas height
5733 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5734
5735 double delta_check =
5736 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5737 delta_check /= 8.;
5738
5739 double check_point = wxMin(89., VPoint.clat);
5740
5741 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5742
5743 double rhumbDist;
5744 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5745 VPoint.clon, 0, &rhumbDist);
5746
5747 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5748 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5749 // Calculate the distance between r1 and r in physical pixels.
5750 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5751 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5752
5753 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5754
5755 // A fall back in case of very high zoom-out, giving delta_y == 0
5756 // which can probably only happen with vector charts
5757 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5758
5759 // Another fallback, for highly zoomed out charts
5760 // This adjustment makes the displayed TrueScale correspond to the
5761 // same algorithm used to calculate the chart zoom-out limit for
5762 // ChartDummy.
5763 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5764
5765 if (m_true_scale_ppm)
5766 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5767 else
5768 VPoint.chart_scale = 1.0;
5769
5770 // Create a nice renderable string
5771 double round_factor = 1000.;
5772 if (VPoint.chart_scale <= 1000.)
5773 round_factor = 10.;
5774 else if (VPoint.chart_scale <= 10000.)
5775 round_factor = 100.;
5776 else if (VPoint.chart_scale <= 100000.)
5777 round_factor = 1000.;
5778
5779 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5780 double retina_coef = 1;
5781#ifdef ocpnUSE_GL
5782#ifdef __WXOSX__
5783 if (g_bopengl) {
5784 retina_coef = GetContentScaleFactor();
5785 }
5786#endif
5787#endif
5788
5789 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5790 // rounded to the nearest 10, 100 or 1000.
5791 //
5792 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5793 // true_scale_display. That does not make sense. The chart scale should be
5794 // the same as the true scale within the limits of the rounding factor.
5795 double true_scale_display =
5796 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5797 wxString text;
5798
5799 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5800
5801 if (m_displayed_scale_factor > 10.0)
5802 text.Printf(_T("%s %4.0f (%1.0fx)"), _("Scale"), true_scale_display,
5803 m_displayed_scale_factor);
5804 else if (m_displayed_scale_factor > 1.0)
5805 text.Printf(_T("%s %4.0f (%1.1fx)"), _("Scale"), true_scale_display,
5806 m_displayed_scale_factor);
5807 else if (m_displayed_scale_factor > 0.1) {
5808 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5809 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5810 } else if (m_displayed_scale_factor > 0.01) {
5811 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5812 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5813 } else {
5814 text.Printf(
5815 _T("%s %4.0f (---)"), _("Scale"),
5816 true_scale_display); // Generally, no chart, so no chart scale factor
5817 }
5818
5819 m_scaleValue = true_scale_display;
5820 m_scaleText = text;
5821 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5822
5823 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5824 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5825 // Check to see if the text will fit in the StatusBar field...
5826 bool b_noshow = false;
5827 {
5828 int w = 0;
5829 int h;
5830 wxClientDC dc(parent_frame->GetStatusBar());
5831 if (dc.IsOk()) {
5832 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5833 dc.SetFont(*templateFont);
5834 dc.GetTextExtent(text, &w, &h);
5835
5836 // If text is too long for the allocated field, try to reduce the text
5837 // string a bit.
5838 wxRect rect;
5839 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5840 if (w && w > rect.width) {
5841 text.Printf(_T("%s (%1.1fx)"), _("Scale"),
5842 m_displayed_scale_factor);
5843 }
5844
5845 // Test again...if too big still, then give it up.
5846 dc.GetTextExtent(text, &w, &h);
5847
5848 if (w && w > rect.width) {
5849 b_noshow = true;
5850 }
5851 }
5852 }
5853
5854 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5855 }
5856 }
5857
5858 // Maintain member vLat/vLon
5859 m_vLat = VPoint.clat;
5860 m_vLon = VPoint.clon;
5861
5862 return b_ret;
5863}
5864
5865// Static Icon definitions for some symbols requiring
5866// scaling/rotation/translation Very specific wxDC draw commands are
5867// necessary to properly render these icons...See the code in
5868// ShipDraw()
5869
5870// This icon was adapted and scaled from the S52 Presentation Library
5871// version 3_03.
5872// Symbol VECGND02
5873
5874static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5875
5876// This ownship icon was adapted and scaled from the S52 Presentation
5877// Library version 3_03 Symbol OWNSHP05
5878static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5879 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5880
5881wxColour ChartCanvas::PredColor() {
5882 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5883 // visibility.
5884 if (SHIP_NORMAL == m_ownship_state)
5885 return GetGlobalColor(_T ( "URED" ));
5886
5887 else if (SHIP_LOWACCURACY == m_ownship_state)
5888 return GetGlobalColor(_T ( "YELO1" ));
5889
5890 return GetGlobalColor(_T ( "NODTA" ));
5891}
5892
5893wxColour ChartCanvas::ShipColor() {
5894 // Establish ship color
5895 // It changes color based on GPS and Chart accuracy/availability
5896
5897 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor(_T ( "GREY1" ));
5898
5899 if (SHIP_LOWACCURACY == m_ownship_state)
5900 return GetGlobalColor(_T ( "YELO1" ));
5901
5902 return GetGlobalColor(_T ( "URED" )); // default is OK
5903}
5904
5905void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5906 wxPoint2DDouble lShipMidPoint) {
5907 dc.SetPen(wxPen(PredColor(), 2));
5908
5909 if (SHIP_NORMAL == m_ownship_state)
5910 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5911 else
5912 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "YELO1" ))));
5913
5914 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5915 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5916
5917 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5918 lShipMidPoint.m_y);
5919 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5920 lShipMidPoint.m_y + 12);
5921}
5922
5923void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5924 wxPoint GPSOffsetPixels,
5925 wxPoint2DDouble lGPSPoint) {
5926 if (m_animationActive) return;
5927 // Develop a uniform length for course predictor line dash length, based on
5928 // physical display size Use this reference length to size all other graphics
5929 // elements
5930 float ref_dim = m_display_size_mm / 24;
5931 ref_dim = wxMin(ref_dim, 12);
5932 ref_dim = wxMax(ref_dim, 6);
5933
5934 wxColour cPred;
5935 cPred.Set(g_cog_predictor_color);
5936 if (cPred == wxNullColour) cPred = PredColor();
5937
5938 // Establish some graphic element line widths dependent on the platform
5939 // display resolution
5940 // double nominal_line_width_pix = wxMax(1.0,
5941 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5942 // not less than 1 pixel
5943 double nominal_line_width_pix = wxMax(
5944 1.0,
5945 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5946
5947 // If the calculated value is greater than the config file spec value, then
5948 // use it.
5949 if (nominal_line_width_pix > g_cog_predictor_width)
5950 g_cog_predictor_width = nominal_line_width_pix;
5951
5952 // Calculate ownship Position Predictor
5953 wxPoint lPredPoint, lHeadPoint;
5954
5955 float pCog = std::isnan(gCog) ? 0 : gCog;
5956 float pSog = std::isnan(gSog) ? 0 : gSog;
5957
5958 double pred_lat, pred_lon;
5959 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5960 &pred_lat, &pred_lon);
5961 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5962
5963 // test to catch the case where COG/HDG line crosses the screen
5964 LLBBox box;
5965
5966 // Should we draw the Head vector?
5967 // Compare the points lHeadPoint and lPredPoint
5968 // If they differ by more than n pixels, and the head vector is valid, then
5969 // render the head vector
5970
5971 float ndelta_pix = 10.;
5972 double hdg_pred_lat, hdg_pred_lon;
5973 bool b_render_hdt = false;
5974 if (!std::isnan(gHdt)) {
5975 // Calculate ownship Heading pointer as a predictor
5976 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5977 &hdg_pred_lon);
5978 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5979 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5980 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5981 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5982 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5983 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5984 }
5985 }
5986
5987 // draw course over ground if they are longer than the ship
5988 wxPoint lShipMidPoint;
5989 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5990 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5991 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5992 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5993
5994 if (lpp >= img_height / 2) {
5995 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5996 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5997 !std::isnan(gSog)) {
5998 // COG Predictor
5999 float dash_length = ref_dim;
6000 wxDash dash_long[2];
6001 dash_long[0] =
6002 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6003 g_cog_predictor_width); // Long dash , in mm <---------+
6004 dash_long[1] = dash_long[0] / 2.0; // Short gap
6005
6006 // On ultra-hi-res displays, do not allow the dashes to be greater than
6007 // 250, since it is defined as (char)
6008 if (dash_length > 250.) {
6009 dash_long[0] = 250. / g_cog_predictor_width;
6010 dash_long[1] = dash_long[0] / 2;
6011 }
6012
6013 wxPen ppPen2(cPred, g_cog_predictor_width,
6014 (wxPenStyle)g_cog_predictor_style);
6015 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6016 ppPen2.SetDashes(2, dash_long);
6017 dc.SetPen(ppPen2);
6018 dc.StrokeLine(
6019 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6020 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6021
6022 if (g_cog_predictor_width > 1) {
6023 float line_width = g_cog_predictor_width / 3.;
6024
6025 wxDash dash_long3[2];
6026 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6027 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6028
6029 wxPen ppPen3(GetGlobalColor(_T ( "UBLCK" )), wxMax(1, line_width),
6030 (wxPenStyle)g_cog_predictor_style);
6031 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6032 ppPen3.SetDashes(2, dash_long3);
6033 dc.SetPen(ppPen3);
6034 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6035 lGPSPoint.m_y + GPSOffsetPixels.y,
6036 lPredPoint.x + GPSOffsetPixels.x,
6037 lPredPoint.y + GPSOffsetPixels.y);
6038 }
6039
6040 if (g_cog_predictor_endmarker) {
6041 // Prepare COG predictor endpoint icon
6042 double png_pred_icon_scale_factor = .4;
6043 if (g_ShipScaleFactorExp > 1.0)
6044 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6045 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6046
6047 wxPoint icon[4];
6048
6049 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6050 (float)(lPredPoint.x - lShipMidPoint.x));
6051 cog_rad += (float)PI;
6052
6053 for (int i = 0; i < 4; i++) {
6054 int j = i * 2;
6055 double pxa = (double)(s_png_pred_icon[j]);
6056 double pya = (double)(s_png_pred_icon[j + 1]);
6057
6058 pya *= png_pred_icon_scale_factor;
6059 pxa *= png_pred_icon_scale_factor;
6060
6061 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6062 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6063
6064 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6065 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6066 }
6067
6068 // Render COG endpoint icon
6069 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), g_cog_predictor_width / 2,
6070 wxPENSTYLE_SOLID);
6071 dc.SetPen(ppPen1);
6072 dc.SetBrush(wxBrush(cPred));
6073
6074 dc.StrokePolygon(4, icon);
6075 }
6076 }
6077 }
6078
6079 // HDT Predictor
6080 if (b_render_hdt) {
6081 float hdt_dash_length = ref_dim * 0.4;
6082
6083 cPred.Set(g_ownship_HDTpredictor_color);
6084 if (cPred == wxNullColour) cPred = PredColor();
6085 float hdt_width =
6086 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6087 : g_cog_predictor_width * 0.8);
6088 wxDash dash_short[2];
6089 dash_short[0] =
6090 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6091 hdt_width); // Short dash , in mm <---------+
6092 dash_short[1] =
6093 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6094 hdt_width); // Short gap |
6095
6096 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6097 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6098 ppPen2.SetDashes(2, dash_short);
6099
6100 dc.SetPen(ppPen2);
6101 dc.StrokeLine(
6102 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6103 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6104
6105 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6106 dc.SetPen(ppPen1);
6107 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "GREY2" ))));
6108
6109 if (g_ownship_HDTpredictor_endmarker) {
6110 double nominal_circle_size_pixels = wxMax(
6111 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6112
6113 // Scale the circle to ChartScaleFactor, slightly softened....
6114 if (g_ShipScaleFactorExp > 1.0)
6115 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6116
6117 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6118 lHeadPoint.y + GPSOffsetPixels.y,
6119 nominal_circle_size_pixels / 2);
6120 }
6121 }
6122
6123 // Draw radar rings if activated
6124 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6125 double factor = 1.00;
6126 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6127 factor = 1 / 1.852;
6128 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6129 if (std::isnan(gSog))
6130 factor = 0.0;
6131 else
6132 factor = gSog / 60;
6133 }
6134 factor *= g_fNavAidRadarRingsStep;
6135
6136 double tlat, tlon;
6137 wxPoint r;
6138 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6139 GetCanvasPointPix(tlat, tlon, &r);
6140
6141 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6142 pow((double)(lGPSPoint.m_y - r.y), 2));
6143 int pix_radius = (int)lpp;
6144
6145 extern wxColor GetDimColor(wxColor c);
6146 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6147
6148 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6149
6150 dc.SetPen(ppPen1);
6151 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6152
6153 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6154 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6155 }
6156}
6157
6158void ChartCanvas::ComputeShipScaleFactor(
6159 float icon_hdt, int ownShipWidth, int ownShipLength,
6160 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6161 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6162 float screenResolution = m_pix_per_mm;
6163
6164 // Calculate the true ship length in exact pixels
6165 double ship_bow_lat, ship_bow_lon;
6166 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6167 &ship_bow_lat, &ship_bow_lon);
6168 wxPoint lShipBowPoint;
6169 wxPoint2DDouble b_point =
6170 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6171 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6172
6173 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6174 powf((float)(b_point.m_y - a_point.m_y), 2));
6175
6176 // And in mm
6177 float shipLength_mm = shipLength_px / screenResolution;
6178
6179 // Set minimum ownship drawing size
6180 float ownship_min_mm = g_n_ownship_min_mm;
6181 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6182
6183 // Calculate Nautical Miles distance from midships to gps antenna
6184 float hdt_ant = icon_hdt + 180.;
6185 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6186 float dx = g_n_gps_antenna_offset_x / 1852.;
6187 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6188 {
6189 hdt_ant = icon_hdt;
6190 dy = -dy;
6191 }
6192
6193 // If the drawn ship size is going to be clamped, adjust the gps antenna
6194 // offsets
6195 if (shipLength_mm < ownship_min_mm) {
6196 dy /= shipLength_mm / ownship_min_mm;
6197 dx /= shipLength_mm / ownship_min_mm;
6198 }
6199
6200 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6201
6202 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6203 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6204 &ship_mid_lon1);
6205
6206 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6207 &lShipMidPoint);
6208
6209 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6210 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6211
6212 float scale_factor = shipLength_px / ownShipLength;
6213
6214 // Calculate a scale factor that would produce a reasonably sized icon
6215 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6216
6217 // And choose the correct one
6218 scale_factor = wxMax(scale_factor, scale_factor_min);
6219
6220 scale_factor_y = scale_factor;
6221 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6222 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6223}
6224
6225void ChartCanvas::ShipDraw(ocpnDC &dc) {
6226 if (!GetVP().IsValid()) return;
6227
6228 wxPoint GPSOffsetPixels(0, 0);
6229 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6230
6231 // COG/SOG may be undefined in NMEA data stream
6232 float pCog = std::isnan(gCog) ? 0 : gCog;
6233 float pSog = std::isnan(gSog) ? 0 : gSog;
6234
6235 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6236
6237 lShipMidPoint = lGPSPoint;
6238
6239 // Draw the icon rotated to the COG
6240 // or to the Hdt if available
6241 float icon_hdt = pCog;
6242 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6243
6244 // COG may be undefined in NMEA data stream
6245 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6246
6247 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6248 // predictor
6249 double osd_head_lat, osd_head_lon;
6250 wxPoint osd_head_point;
6251
6252 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6253 &osd_head_lon);
6254
6255 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6256
6257 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6258 (float)(osd_head_point.x - lShipMidPoint.m_x));
6259 icon_rad += (float)PI;
6260
6261 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6262
6263 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6264 // nominal size and is just barely outside the viewport ....
6265 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6266
6267 // TODO: fix to include actual size of boat that will be rendered
6268 int img_height = 0;
6269 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6270 if (GetVP().chart_scale >
6271 300000) // According to S52, this should be 50,000
6272 {
6273 ShipDrawLargeScale(dc, lShipMidPoint);
6274 img_height = 20;
6275 } else {
6276 wxImage pos_image;
6277
6278 // Substitute user ownship image if found
6279 if (m_pos_image_user)
6280 pos_image = m_pos_image_user->Copy();
6281 else if (SHIP_NORMAL == m_ownship_state)
6282 pos_image = m_pos_image_red->Copy();
6283 if (SHIP_LOWACCURACY == m_ownship_state)
6284 pos_image = m_pos_image_yellow->Copy();
6285 else if (SHIP_NORMAL != m_ownship_state)
6286 pos_image = m_pos_image_grey->Copy();
6287
6288 // Substitute user ownship image if found
6289 if (m_pos_image_user) {
6290 pos_image = m_pos_image_user->Copy();
6291
6292 if (SHIP_LOWACCURACY == m_ownship_state)
6293 pos_image = m_pos_image_user_yellow->Copy();
6294 else if (SHIP_NORMAL != m_ownship_state)
6295 pos_image = m_pos_image_user_grey->Copy();
6296 }
6297
6298 img_height = pos_image.GetHeight();
6299
6300 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6301 g_OwnShipIconType > 0) // use large ship
6302 {
6303 int ownShipWidth = 22; // Default values from s_ownship_icon
6304 int ownShipLength = 84;
6305 if (g_OwnShipIconType == 1) {
6306 ownShipWidth = pos_image.GetWidth();
6307 ownShipLength = pos_image.GetHeight();
6308 }
6309
6310 float scale_factor_x, scale_factor_y;
6311 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6312 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6313 scale_factor_x, scale_factor_y);
6314
6315 if (g_OwnShipIconType == 1) { // Scaled bitmap
6316 pos_image.Rescale(ownShipWidth * scale_factor_x,
6317 ownShipLength * scale_factor_y,
6318 wxIMAGE_QUALITY_HIGH);
6319 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6320 wxImage rot_image =
6321 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6322
6323 // Simple sharpening algorithm.....
6324 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6325 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6326 if (rot_image.GetAlpha(ip, jp) > 64)
6327 rot_image.SetAlpha(ip, jp, 255);
6328
6329 wxBitmap os_bm(rot_image);
6330
6331 int w = os_bm.GetWidth();
6332 int h = os_bm.GetHeight();
6333 img_height = h;
6334
6335 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6336 lShipMidPoint.m_y - h / 2, true);
6337
6338 // Maintain dirty box,, missing in __WXMSW__ library
6339 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6340 lShipMidPoint.m_y - h / 2);
6341 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6342 lShipMidPoint.m_y - h / 2 + h);
6343 }
6344
6345 else if (g_OwnShipIconType == 2) { // Scaled Vector
6346 wxPoint ownship_icon[10];
6347
6348 for (int i = 0; i < 10; i++) {
6349 int j = i * 2;
6350 float pxa = (float)(s_ownship_icon[j]);
6351 float pya = (float)(s_ownship_icon[j + 1]);
6352 pya *= scale_factor_y;
6353 pxa *= scale_factor_x;
6354
6355 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6356 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6357
6358 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6359 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6360 }
6361
6362 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), 1, wxPENSTYLE_SOLID);
6363 dc.SetPen(ppPen1);
6364 dc.SetBrush(wxBrush(ShipColor()));
6365
6366 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6367
6368 // draw reference point (midships) cross
6369 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6370 ownship_icon[7].y);
6371 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6372 ownship_icon[9].y);
6373 }
6374
6375 img_height = ownShipLength * scale_factor_y;
6376
6377 // Reference point, where the GPS antenna is
6378 int circle_rad = 3;
6379 if (m_pos_image_user) circle_rad = 1;
6380
6381 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6382 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6383 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6384 } else { // Fixed bitmap icon.
6385 /* non opengl, or suboptimal opengl via ocpndc: */
6386 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6387 wxImage rot_image =
6388 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6389
6390 // Simple sharpening algorithm.....
6391 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6392 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6393 if (rot_image.GetAlpha(ip, jp) > 64)
6394 rot_image.SetAlpha(ip, jp, 255);
6395
6396 wxBitmap os_bm(rot_image);
6397
6398 if (g_ShipScaleFactorExp > 1) {
6399 wxImage scaled_image = os_bm.ConvertToImage();
6400 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6401 1.0; // soften the scale factor a bit
6402 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6403 scaled_image.GetHeight() * factor,
6404 wxIMAGE_QUALITY_HIGH));
6405 }
6406 int w = os_bm.GetWidth();
6407 int h = os_bm.GetHeight();
6408 img_height = h;
6409
6410 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6411 lShipMidPoint.m_y - h / 2, true);
6412
6413 // Reference point, where the GPS antenna is
6414 int circle_rad = 3;
6415 if (m_pos_image_user) circle_rad = 1;
6416
6417 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6418 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6419 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6420
6421 // Maintain dirty box,, missing in __WXMSW__ library
6422 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6423 lShipMidPoint.m_y - h / 2);
6424 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6425 lShipMidPoint.m_y - h / 2 + h);
6426 }
6427 } // ownship draw
6428 }
6429
6430 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6431}
6432
6433/* @ChartCanvas::CalcGridSpacing
6434 **
6435 ** Calculate the major and minor spacing between the lat/lon grid
6436 **
6437 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6438 *window
6439 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6440 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6441 ** @return [void]
6442 */
6443void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6444 float &MinorSpacing) {
6445 // table for calculating the distance between the grids
6446 // [0] view_scale ppm
6447 // [1] spacing between major grid lines in degrees
6448 // [2] spacing between minor grid lines in degrees
6449 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6450 {.000001f, 45.0f, 15.0f},
6451 {.0002f, 30.0f, 10.0f},
6452 {.0003f, 10.0f, 2.0f},
6453 {.0008f, 5.0f, 1.0f},
6454 {.001f, 2.0f, 30.0f / 60.0f},
6455 {.003f, 1.0f, 20.0f / 60.0f},
6456 {.006f, 0.5f, 10.0f / 60.0f},
6457 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6458 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6459 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6460 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6461 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6462 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6463 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6464 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6465
6466 unsigned int tabi;
6467 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6468 if (view_scale_ppm < lltab[tabi][0]) break;
6469 MajorSpacing = lltab[tabi][1]; // major latitude distance
6470 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6471 return;
6472}
6473/* @ChartCanvas::CalcGridText *************************************
6474 **
6475 ** Calculates text to display at the major grid lines
6476 **
6477 ** @param [r] latlon [float] latitude or longitude of grid line
6478 ** @param [r] spacing [float] distance between two major grid lines
6479 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6480 **
6481 ** @return
6482 */
6483
6484wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6485 int deg = (int)fabs(latlon); // degrees
6486 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6487 char postfix;
6488
6489 // calculate postfix letter (NSEW)
6490 if (latlon > 0.0) {
6491 if (bPostfix) {
6492 postfix = 'N';
6493 } else {
6494 postfix = 'E';
6495 }
6496 } else if (latlon < 0.0) {
6497 if (bPostfix) {
6498 postfix = 'S';
6499 } else {
6500 postfix = 'W';
6501 }
6502 } else {
6503 postfix = ' '; // no postfix for equator and greenwich
6504 }
6505 // calculate text, display minutes only if spacing is smaller than one degree
6506
6507 wxString ret;
6508 if (spacing >= 1.0) {
6509 ret.Printf(_T("%3d%c %c"), deg, 0x00b0, postfix);
6510 } else if (spacing >= (1.0 / 60.0)) {
6511 ret.Printf(_T("%3d%c%02.0f %c"), deg, 0x00b0, min, postfix);
6512 } else {
6513 ret.Printf(_T("%3d%c%02.2f %c"), deg, 0x00b0, min, postfix);
6514 }
6515
6516 return ret;
6517}
6518
6519/* @ChartCanvas::GridDraw *****************************************
6520 **
6521 ** Draws major and minor Lat/Lon Grid on the chart
6522 ** - distance between Grid-lm ines are calculated automatic
6523 ** - major grid lines will be across the whole chart window
6524 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6525 **
6526 ** @param [w] dc [wxDC&] the wx drawing context
6527 **
6528 ** @return [void]
6529 ************************************************************************/
6530void ChartCanvas::GridDraw(ocpnDC &dc) {
6531 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6532
6533 double nlat, elon, slat, wlon;
6534 float lat, lon;
6535 float dlon;
6536 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6537 wxCoord w, h;
6538 wxPen GridPen(GetGlobalColor(_T ( "SNDG1" )), 1, wxPENSTYLE_SOLID);
6539 dc.SetPen(GridPen);
6540 dc.SetFont(*m_pgridFont);
6541 dc.SetTextForeground(GetGlobalColor(_T ( "SNDG1" )));
6542
6543 w = m_canvas_width;
6544 h = m_canvas_height;
6545
6546 GetCanvasPixPoint(0, 0, nlat,
6547 wlon); // get lat/lon of upper left point of the window
6548 GetCanvasPixPoint(w, h, slat,
6549 elon); // get lat/lon of lower right point of the window
6550 dlon =
6551 elon -
6552 wlon; // calculate how many degrees of longitude are shown in the window
6553 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6554 {
6555 dlon = dlon + 360.0;
6556 }
6557 // calculate distance between latitude grid lines
6558 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6559
6560 // calculate position of first major latitude grid line
6561 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6562
6563 // Draw Major latitude grid lines and text
6564 while (lat < nlat) {
6565 wxPoint r;
6566 wxString st =
6567 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6568 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6569 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6570 dc.DrawText(st, 0, r.y); // draw text
6571 lat = lat + gridlatMajor;
6572
6573 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6574 }
6575
6576 // calculate position of first minor latitude grid line
6577 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6578
6579 // Draw minor latitude grid lines
6580 while (lat < nlat) {
6581 wxPoint r;
6582 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6583 dc.DrawLine(0, r.y, 10, r.y, false);
6584 dc.DrawLine(w - 10, r.y, w, r.y, false);
6585 lat = lat + gridlatMinor;
6586 }
6587
6588 // calculate distance between grid lines
6589 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6590
6591 // calculate position of first major latitude grid line
6592 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6593
6594 // draw major longitude grid lines
6595 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6596 wxPoint r;
6597 wxString st = CalcGridText(lon, gridlonMajor, false);
6598 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6599 dc.DrawLine(r.x, 0, r.x, h, false);
6600 dc.DrawText(st, r.x, 0);
6601 lon = lon + gridlonMajor;
6602 if (lon > 180.0) {
6603 lon = lon - 360.0;
6604 }
6605
6606 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6607 }
6608
6609 // calculate position of first minor longitude grid line
6610 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6611 // draw minor longitude grid lines
6612 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6613 wxPoint r;
6614 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6615 dc.DrawLine(r.x, 0, r.x, 10, false);
6616 dc.DrawLine(r.x, h - 10, r.x, h, false);
6617 lon = lon + gridlonMinor;
6618 if (lon > 180.0) {
6619 lon = lon - 360.0;
6620 }
6621 }
6622}
6623
6624void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6625 if (0 ) {
6626 double blat, blon, tlat, tlon;
6627 wxPoint r;
6628
6629 int x_origin = m_bDisplayGrid ? 60 : 20;
6630 int y_origin = m_canvas_height - 50;
6631
6632 float dist;
6633 int count;
6634 wxPen pen1, pen2;
6635
6636 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6637 {
6638 dist = 10.0;
6639 count = 5;
6640 pen1 = wxPen(GetGlobalColor(_T ( "SNDG2" )), 3, wxPENSTYLE_SOLID);
6641 pen2 = wxPen(GetGlobalColor(_T ( "SNDG1" )), 3, wxPENSTYLE_SOLID);
6642 } else // Draw 1 mile scale as SCALEB10
6643 {
6644 dist = 1.0;
6645 count = 10;
6646 pen1 = wxPen(GetGlobalColor(_T ( "SCLBR" )), 3, wxPENSTYLE_SOLID);
6647 pen2 = wxPen(GetGlobalColor(_T ( "CHGRD" )), 3, wxPENSTYLE_SOLID);
6648 }
6649
6650 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6651 double rotation = -VPoint.rotation;
6652
6653 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6654 GetCanvasPointPix(tlat, tlon, &r);
6655 int l1 = (y_origin - r.y) / count;
6656
6657 for (int i = 0; i < count; i++) {
6658 int y = l1 * i;
6659 if (i & 1)
6660 dc.SetPen(pen1);
6661 else
6662 dc.SetPen(pen2);
6663
6664 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6665 }
6666 } else {
6667 double blat, blon, tlat, tlon;
6668
6669 int x_origin = 5.0 * GetPixPerMM();
6670 int chartbar_height = GetChartbarHeight();
6671 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6672 // if (style->chartStatusWindowTransparent)
6673 // chartbar_height = 0;
6674 int y_origin = m_canvas_height - chartbar_height - 5;
6675#ifdef __WXOSX__
6676 if (!g_bopengl)
6677 y_origin =
6678 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6679#endif
6680
6681 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6682 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6683
6684 double d;
6685 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6686 d /= 2;
6687
6688 int unit = g_iDistanceFormat;
6689 if (d < .5 &&
6690 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6691 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6692
6693 // nice number
6694 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6695 float places = floor(logdist), rem = logdist - places;
6696 dist = pow(10, places);
6697
6698 if (rem < .2)
6699 dist /= 5;
6700 else if (rem < .5)
6701 dist /= 2;
6702
6703 wxString s = wxString::Format(_T("%g "), dist) + getUsrDistanceUnit(unit);
6704 wxPen pen1 = wxPen(GetGlobalColor(_T ( "UBLCK" )), 3, wxPENSTYLE_SOLID);
6705 double rotation = -VPoint.rotation;
6706
6707 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6708 &tlat, &tlon);
6709 wxPoint r;
6710 GetCanvasPointPix(tlat, tlon, &r);
6711 int l1 = r.x - x_origin;
6712
6713 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6714 12); // Store this for later reference
6715
6716 dc.SetPen(pen1);
6717
6718 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6719 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6720 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6721
6722 dc.SetFont(*m_pgridFont);
6723 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
6724 int w, h;
6725 dc.GetTextExtent(s, &w, &h);
6726 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6727 }
6728}
6729
6730void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6731 // Constants?
6732 double da_min = 2.;
6733 double da_max = 6.;
6734 double ra_min = 0.;
6735 double ra_max = 40.;
6736
6737 wxPen pen_save = dc.GetPen();
6738
6739 wxDateTime now = wxDateTime::Now();
6740
6741 dc.SetPen(pen);
6742
6743 int x0, y0, x1, y1;
6744
6745 x0 = x1 = x + radius; // Start point
6746 y0 = y1 = y;
6747 double angle = 0.;
6748 int i = 0;
6749
6750 while (angle < 360.) {
6751 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6752 angle += da;
6753
6754 if (angle > 360.) angle = 360.;
6755
6756 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6757
6758 double r;
6759 if (i & 1)
6760 r = radius + ra;
6761 else
6762 r = radius - ra;
6763
6764 x1 = (int)(x + cos(angle * PI / 180.) * r);
6765 y1 = (int)(y + sin(angle * PI / 180.) * r);
6766
6767 dc.DrawLine(x0, y0, x1, y1);
6768
6769 x0 = x1;
6770 y0 = y1;
6771
6772 i++;
6773 }
6774
6775 dc.DrawLine(x + radius, y, x1, y1); // closure
6776
6777 dc.SetPen(pen_save);
6778}
6779
6780static bool bAnchorSoundPlaying = false;
6781
6782static void onAnchorSoundFinished(void *ptr) {
6783 g_anchorwatch_sound->UnLoad();
6784 bAnchorSoundPlaying = false;
6785}
6786
6787void ChartCanvas::AlertDraw(ocpnDC &dc) {
6788 // Visual and audio alert for anchorwatch goes here
6789 bool play_sound = false;
6790 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6791 if (AnchorAlertOn1) {
6792 wxPoint TargetPoint;
6793 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
6794 &TargetPoint);
6795 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6796 TargetPoint.y, 100);
6797 play_sound = true;
6798 }
6799 } else
6800 AnchorAlertOn1 = false;
6801
6802 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6803 if (AnchorAlertOn2) {
6804 wxPoint TargetPoint;
6805 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
6806 &TargetPoint);
6807 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6808 TargetPoint.y, 100);
6809 play_sound = true;
6810 }
6811 } else
6812 AnchorAlertOn2 = false;
6813
6814 if (play_sound) {
6815 if (!bAnchorSoundPlaying) {
6816 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6817 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6818 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6819 if (g_anchorwatch_sound->IsOk()) {
6820 bAnchorSoundPlaying = true;
6821 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6822 g_anchorwatch_sound->Play();
6823 }
6824 }
6825 }
6826}
6827
6828void ChartCanvas::UpdateShips() {
6829 // Get the rectangle in the current dc which bounds the "ownship" symbol
6830
6831 wxClientDC dc(this);
6832 if (!dc.IsOk()) return;
6833
6834 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6835 if (!test_bitmap.IsOk()) return;
6836
6837 wxMemoryDC temp_dc(test_bitmap);
6838
6839 temp_dc.ResetBoundingBox();
6840 temp_dc.DestroyClippingRegion();
6841 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6842
6843 // Draw the ownship on the temp_dc
6844 ocpnDC ocpndc = ocpnDC(temp_dc);
6845 ShipDraw(ocpndc);
6846
6847 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6848 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6849 if (p) {
6850 wxPoint px;
6851 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6852 ocpndc.CalcBoundingBox(px.x, px.y);
6853 }
6854 }
6855
6856 ship_draw_rect =
6857 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6858 temp_dc.MaxY() - temp_dc.MinY());
6859
6860 wxRect own_ship_update_rect = ship_draw_rect;
6861
6862 if (!own_ship_update_rect.IsEmpty()) {
6863 // The required invalidate rectangle is the union of the last drawn
6864 // rectangle and this drawn rectangle
6865 own_ship_update_rect.Union(ship_draw_last_rect);
6866 own_ship_update_rect.Inflate(2);
6867 }
6868
6869 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6870
6871 ship_draw_last_rect = ship_draw_rect;
6872
6873 temp_dc.SelectObject(wxNullBitmap);
6874}
6875
6876void ChartCanvas::UpdateAlerts() {
6877 // Get the rectangle in the current dc which bounds the detected Alert
6878 // targets
6879
6880 // Use this dc
6881 wxClientDC dc(this);
6882
6883 // Get dc boundary
6884 int sx, sy;
6885 dc.GetSize(&sx, &sy);
6886
6887 // Need a bitmap
6888 wxBitmap test_bitmap(sx, sy, -1);
6889
6890 // Create a memory DC
6891 wxMemoryDC temp_dc;
6892 temp_dc.SelectObject(test_bitmap);
6893
6894 temp_dc.ResetBoundingBox();
6895 temp_dc.DestroyClippingRegion();
6896 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6897
6898 // Draw the Alert Targets on the temp_dc
6899 ocpnDC ocpndc = ocpnDC(temp_dc);
6900 AlertDraw(ocpndc);
6901
6902 // Retrieve the drawing extents
6903 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6904 temp_dc.MaxX() - temp_dc.MinX(),
6905 temp_dc.MaxY() - temp_dc.MinY());
6906
6907 if (!alert_rect.IsEmpty())
6908 alert_rect.Inflate(2); // clear all drawing artifacts
6909
6910 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6911 // The required invalidate rectangle is the union of the last drawn
6912 // rectangle and this drawn rectangle
6913 wxRect alert_update_rect = alert_draw_rect;
6914 alert_update_rect.Union(alert_rect);
6915
6916 // Invalidate the rectangular region
6917 RefreshRect(alert_update_rect, false);
6918 }
6919
6920 // Save this rectangle for next time
6921 alert_draw_rect = alert_rect;
6922
6923 temp_dc.SelectObject(wxNullBitmap); // clean up
6924}
6925
6926void ChartCanvas::UpdateAIS() {
6927 if (!g_pAIS) return;
6928
6929 // Get the rectangle in the current dc which bounds the detected AIS targets
6930
6931 // Use this dc
6932 wxClientDC dc(this);
6933
6934 // Get dc boundary
6935 int sx, sy;
6936 dc.GetSize(&sx, &sy);
6937
6938 wxRect ais_rect;
6939
6940 // How many targets are there?
6941
6942 // If more than "some number", it will be cheaper to refresh the entire
6943 // screen than to build update rectangles for each target.
6944 if (g_pAIS->GetTargetList().size() > 10) {
6945 ais_rect = wxRect(0, 0, sx, sy); // full screen
6946 } else {
6947 // Need a bitmap
6948 wxBitmap test_bitmap(sx, sy, -1);
6949
6950 // Create a memory DC
6951 wxMemoryDC temp_dc;
6952 temp_dc.SelectObject(test_bitmap);
6953
6954 temp_dc.ResetBoundingBox();
6955 temp_dc.DestroyClippingRegion();
6956 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6957
6958 // Draw the AIS Targets on the temp_dc
6959 ocpnDC ocpndc = ocpnDC(temp_dc);
6960 AISDraw(ocpndc, GetVP(), this);
6961 AISDrawAreaNotices(ocpndc, GetVP(), this);
6962
6963 // Retrieve the drawing extents
6964 ais_rect =
6965 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6966 temp_dc.MaxY() - temp_dc.MinY());
6967
6968 if (!ais_rect.IsEmpty())
6969 ais_rect.Inflate(2); // clear all drawing artifacts
6970
6971 temp_dc.SelectObject(wxNullBitmap); // clean up
6972 }
6973
6974 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6975 // The required invalidate rectangle is the union of the last drawn
6976 // rectangle and this drawn rectangle
6977 wxRect ais_update_rect = ais_draw_rect;
6978 ais_update_rect.Union(ais_rect);
6979
6980 // Invalidate the rectangular region
6981 RefreshRect(ais_update_rect, false);
6982 }
6983
6984 // Save this rectangle for next time
6985 ais_draw_rect = ais_rect;
6986}
6987
6988void ChartCanvas::ToggleCPAWarn() {
6989 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6990 wxString mess;
6991 if (g_bCPAWarn) {
6992 g_bTCPA_Max = true;
6993 mess = _("ON");
6994 } else {
6995 g_bTCPA_Max = false;
6996 mess = _("OFF");
6997 }
6998 // Print to status bar if available.
6999 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
7000 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7001 } else {
7002 if (!g_AisFirstTimeUse) {
7003 OCPNMessageBox(this,
7004 _("CPA Alarm is switched") + _T(" ") + mess.MakeLower(),
7005 _("CPA") + _T(" ") + mess, 4, 4);
7006 }
7007 }
7008}
7009
7010void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7011
7012void ChartCanvas::OnSize(wxSizeEvent &event) {
7013 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7014 // GetClientSize returns the size of the canvas area in logical pixels.
7015 GetClientSize(&m_canvas_width, &m_canvas_height);
7016
7017#ifdef __WXOSX__
7018 // Support scaled HDPI displays.
7019 m_displayScale = GetContentScaleFactor();
7020#endif
7021
7022 // Convert to physical pixels.
7023 m_canvas_width *= m_displayScale;
7024 m_canvas_height *= m_displayScale;
7025
7026 // Resize the current viewport
7027 VPoint.pix_width = m_canvas_width;
7028 VPoint.pix_height = m_canvas_height;
7029 VPoint.SetPixelScale(m_displayScale);
7030
7031 // Get some canvas metrics
7032
7033 // Rescale to current value, in order to rebuild VPoint data
7034 // structures for new canvas size
7036
7037 m_absolute_min_scale_ppm =
7038 m_canvas_width /
7039 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7040
7041 // Inform the parent Frame that I am being resized...
7042 gFrame->ProcessCanvasResize();
7043
7044 // if MUIBar is active, size the bar
7045 // if(g_useMUI && !m_muiBar){ // rebuild if
7046 // necessary
7047 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7048 // m_muiBarHOSize = m_muiBar->GetSize();
7049 // }
7050
7051 if (m_muiBar) {
7052 SetMUIBarPosition();
7053 UpdateFollowButtonState();
7054 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7055 }
7056
7057 // Set up the scroll margins
7058 xr_margin = m_canvas_width * 95 / 100;
7059 xl_margin = m_canvas_width * 5 / 100;
7060 yt_margin = m_canvas_height * 5 / 100;
7061 yb_margin = m_canvas_height * 95 / 100;
7062
7063 if (m_pQuilt)
7064 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7065
7066 // Resize the scratch BM
7067 delete pscratch_bm;
7068 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7069 m_brepaint_piano = true;
7070
7071 // Resize the Route Calculation BM
7072 m_dc_route.SelectObject(wxNullBitmap);
7073 delete proute_bm;
7074 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7075 m_dc_route.SelectObject(*proute_bm);
7076
7077 // Resize the saved Bitmap
7078 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7079
7080 // Resize the working Bitmap
7081 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7082
7083 // Rescale again, to capture all the changes for new canvas size
7085
7086#ifdef ocpnUSE_GL
7087 if (/*g_bopengl &&*/ m_glcc) {
7088 // FIXME (dave) This can go away?
7089 m_glcc->OnSize(event);
7090 }
7091#endif
7092
7093 FormatPianoKeys();
7094 // Invalidate the whole window
7095 ReloadVP();
7096}
7097
7098void ChartCanvas::ProcessNewGUIScale() {
7099 // m_muiBar->Hide();
7100 delete m_muiBar;
7101 m_muiBar = 0;
7102
7103 CreateMUIBar();
7104}
7105
7106void ChartCanvas::CreateMUIBar() {
7107 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7108
7109 // We need to update the m_bENCGroup flag, at least for the initial creation
7110 // of a MUIBar
7111 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7112
7113 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7114 m_muiBar->SetColorScheme(m_cs);
7115 m_muiBarHOSize = m_muiBar->m_size;
7116 }
7117
7118 if (m_muiBar) {
7119 SetMUIBarPosition();
7120 UpdateFollowButtonState();
7121 m_muiBar->UpdateDynamicValues();
7122 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7123 }
7124}
7125
7126void ChartCanvas::SetMUIBarPosition() {
7127 // if MUIBar is active, size the bar
7128 if (m_muiBar) {
7129 // We estimate the piano width based on the canvas width
7130 int pianoWidth = GetClientSize().x * 0.6f;
7131 // If the piano already exists, we can use its exact width
7132 // if(m_Piano)
7133 // pianoWidth = m_Piano->GetWidth();
7134
7135 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7136 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7137 delete m_muiBar;
7138 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7139 m_muiBar->SetColorScheme(m_cs);
7140 }
7141 }
7142
7143 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7144 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7145 delete m_muiBar;
7146 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7147 m_muiBar->SetColorScheme(m_cs);
7148 }
7149 }
7150
7151 m_muiBar->SetBestPosition();
7152 }
7153}
7154
7155void ChartCanvas::DestroyMuiBar() {
7156 if (m_muiBar) {
7157 delete m_muiBar;
7158 m_muiBar = NULL;
7159 }
7160}
7161
7162void ChartCanvas::ShowCompositeInfoWindow(
7163 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7164 if (n_charts > 0) {
7165 if (NULL == m_pCIWin) {
7166 m_pCIWin = new ChInfoWin(this);
7167 m_pCIWin->Hide();
7168 }
7169
7170 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7171 wxString s;
7172
7173 s = _("Composite of ");
7174
7175 wxString s1;
7176 s1.Printf("%d ", n_charts);
7177 if (n_charts > 1)
7178 s1 += _("charts");
7179 else
7180 s1 += _("chart");
7181 s += s1;
7182 s += '\n';
7183
7184 s1.Printf(_("Chart scale"));
7185 s1 += ": ";
7186 wxString s2;
7187 s2.Printf("1:%d\n", scale);
7188 s += s1;
7189 s += s2;
7190
7191 s1 = _("Zoom in for more information");
7192 s += s1;
7193 s += '\n';
7194
7195 int char_width = s1.Length();
7196 int char_height = 3;
7197
7198 if (g_bChartBarEx) {
7199 s += '\n';
7200 int j = 0;
7201 for (int i : index_vector) {
7202 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7203 wxString path = cte.GetFullSystemPath();
7204 s += path;
7205 s += '\n';
7206 char_height++;
7207 char_width = wxMax(char_width, path.Length());
7208 if (j++ >= 9) break;
7209 }
7210 if (j >= 9) {
7211 s += " .\n .\n .\n";
7212 char_height += 3;
7213 }
7214 s += '\n';
7215 char_height += 1;
7216
7217 char_width += 4; // Fluff
7218 }
7219
7220 m_pCIWin->SetString(s);
7221
7222 m_pCIWin->FitToChars(char_width, char_height);
7223
7224 wxPoint p;
7225 p.x = x / GetContentScaleFactor();
7226 if ((p.x + m_pCIWin->GetWinSize().x) >
7227 (m_canvas_width / GetContentScaleFactor()))
7228 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7229 m_pCIWin->GetWinSize().x) /
7230 2; // centered
7231
7232 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7233 4 - m_pCIWin->GetWinSize().y;
7234
7235 m_pCIWin->dbIndex = 0;
7236 m_pCIWin->chart_scale = 0;
7237 m_pCIWin->SetPosition(p);
7238 m_pCIWin->SetBitmap();
7239 m_pCIWin->Refresh();
7240 m_pCIWin->Show();
7241 }
7242 } else {
7243 HideChartInfoWindow();
7244 }
7245}
7246
7247void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7248 if (dbIndex >= 0) {
7249 if (NULL == m_pCIWin) {
7250 m_pCIWin = new ChInfoWin(this);
7251 m_pCIWin->Hide();
7252 }
7253
7254 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7255 wxString s;
7256 ChartBase *pc = NULL;
7257
7258 // TOCTOU race but worst case will reload chart.
7259 // need to lock it or the background spooler may evict charts in
7260 // OpenChartFromDBAndLock
7261 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7262 pc = ChartData->OpenChartFromDBAndLock(
7263 dbIndex, FULL_INIT); // this must come from cache
7264
7265 int char_width, char_height;
7266 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7267 if (pc) ChartData->UnLockCacheChart(dbIndex);
7268
7269 m_pCIWin->SetString(s);
7270 m_pCIWin->FitToChars(char_width, char_height);
7271
7272 wxPoint p;
7273 p.x = x / GetContentScaleFactor();
7274 if ((p.x + m_pCIWin->GetWinSize().x) >
7275 (m_canvas_width / GetContentScaleFactor()))
7276 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7277 m_pCIWin->GetWinSize().x) /
7278 2; // centered
7279
7280 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7281 4 - m_pCIWin->GetWinSize().y;
7282
7283 m_pCIWin->dbIndex = dbIndex;
7284 m_pCIWin->SetPosition(p);
7285 m_pCIWin->SetBitmap();
7286 m_pCIWin->Refresh();
7287 m_pCIWin->Show();
7288 }
7289 } else {
7290 HideChartInfoWindow();
7291 }
7292}
7293
7294void ChartCanvas::HideChartInfoWindow(void) {
7295 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7296 m_pCIWin->Hide();
7297 m_pCIWin->Destroy();
7298 m_pCIWin = NULL;
7299
7300#ifdef __ANDROID__
7301 androidForceFullRepaint();
7302#endif
7303 }
7304}
7305
7306void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7307 wxMouseEvent ev(wxEVT_MOTION);
7308 ev.m_x = mouse_x;
7309 ev.m_y = mouse_y;
7310 ev.m_leftDown = mouse_leftisdown;
7311
7312 wxEvtHandler *evthp = GetEventHandler();
7313
7314 ::wxPostEvent(evthp, ev);
7315}
7316
7317void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7318 if ((m_panx_target_final - m_panx_target_now) ||
7319 (m_pany_target_final - m_pany_target_now)) {
7320 DoTimedMovementTarget();
7321 } else
7322 DoTimedMovement();
7323}
7324
7325void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7326
7327bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7328 int delta) {
7329 if (m_disable_edge_pan) return false;
7330
7331 bool bft = false;
7332 int pan_margin = m_canvas_width * margin / 100;
7333 int pan_timer_set = 200;
7334 double pan_delta = GetVP().pix_width * delta / 100;
7335 int pan_x = 0;
7336 int pan_y = 0;
7337
7338 if (x > m_canvas_width - pan_margin) {
7339 bft = true;
7340 pan_x = pan_delta;
7341 }
7342
7343 else if (x < pan_margin) {
7344 bft = true;
7345 pan_x = -pan_delta;
7346 }
7347
7348 if (y < pan_margin) {
7349 bft = true;
7350 pan_y = -pan_delta;
7351 }
7352
7353 else if (y > m_canvas_height - pan_margin) {
7354 bft = true;
7355 pan_y = pan_delta;
7356 }
7357
7358 // Of course, if dragging, and the mouse left button is not down, we must
7359 // stop the event injection
7360 if (bdragging) {
7361 if (!g_btouch) {
7362 wxMouseState state = ::wxGetMouseState();
7363#if wxCHECK_VERSION(3, 0, 0)
7364 if (!state.LeftIsDown())
7365#else
7366 if (!state.LeftDown())
7367#endif
7368 bft = false;
7369 }
7370 }
7371 if ((bft) && !pPanTimer->IsRunning()) {
7372 PanCanvas(pan_x, pan_y);
7373 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7374 return true;
7375 }
7376
7377 // This mouse event must not be due to pan timer event injector
7378 // Mouse is out of the pan zone, so prevent any orphan event injection
7379 if ((!bft) && pPanTimer->IsRunning()) {
7380 pPanTimer->Stop();
7381 }
7382
7383 return (false);
7384}
7385
7386// Look for waypoints at the current position.
7387// Used to determine what a mouse event should act on.
7388
7389void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7390 bool setBeingEdited) {
7391 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7392 m_pRoutePointEditTarget = NULL;
7393 m_pFoundPoint = NULL;
7394
7395 SelectItem *pFind = NULL;
7396 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7397 SelectableItemList SelList = pSelect->FindSelectionList(
7398 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7399 wxSelectableItemListNode *node = SelList.GetFirst();
7400 while (node) {
7401 pFind = node->GetData();
7402
7403 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7404
7405 // Get an array of all routes using this point
7406 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7407 // TODO: delete m_pEditRouteArray after use?
7408
7409 // Use route array to determine actual visibility for the point
7410 bool brp_viz = false;
7411 if (m_pEditRouteArray) {
7412 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7413 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7414 if (pr->IsVisible()) {
7415 brp_viz = true;
7416 break;
7417 }
7418 }
7419 } else
7420 brp_viz = frp->IsVisible(); // isolated point
7421
7422 if (brp_viz) {
7423 // Use route array to rubberband all affected routes
7424 if (m_pEditRouteArray) // Editing Waypoint as part of route
7425 {
7426 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7427 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7428 pr->m_bIsBeingEdited = setBeingEdited;
7429 }
7430 m_bRouteEditing = setBeingEdited;
7431 } else // editing Mark
7432 {
7433 frp->m_bRPIsBeingEdited = setBeingEdited;
7434 m_bMarkEditing = setBeingEdited;
7435 }
7436
7437 m_pRoutePointEditTarget = frp;
7438 m_pFoundPoint = pFind;
7439 break; // out of the while(node)
7440 }
7441
7442 node = node->GetNext();
7443 } // while (node)
7444}
7445
7446void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7447 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7448 singleClickEventIsValid = false;
7449 m_DoubleClickTimer->Stop();
7450}
7451
7452bool leftIsDown;
7453
7454bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7455 if (!m_bChartDragging && !m_bDrawingRoute) {
7456 /*
7457 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7458 * mouse event coordinates are in logical pixels.
7459 */
7460 if (m_Compass && m_Compass->IsShown()) {
7461 wxRect logicalRect = m_Compass->GetLogicalRect();
7462 bool isInCompass = logicalRect.Contains(event.GetPosition());
7463 if (isInCompass) {
7464 if (m_Compass->MouseEvent(event)) {
7465 cursor_region = CENTER;
7466 if (!g_btouch) SetCanvasCursor(event);
7467 return true;
7468 }
7469 }
7470 }
7471
7472 if (m_notification_button && m_notification_button->IsShown()) {
7473 wxRect logicalRect = m_notification_button->GetLogicalRect();
7474 bool isinButton = logicalRect.Contains(event.GetPosition());
7475 if (isinButton) {
7476 SetCursor(*pCursorArrow);
7477 if (event.LeftDown()) HandleNotificationMouseClick();
7478 return true;
7479 }
7480 }
7481
7482 if (MouseEventToolbar(event)) return true;
7483
7484 if (MouseEventChartBar(event)) return true;
7485
7486 if (MouseEventMUIBar(event)) return true;
7487
7488 if (MouseEventIENCBar(event)) return true;
7489 }
7490 return false;
7491}
7492
7493void ChartCanvas::HandleNotificationMouseClick() {
7494 if (!m_NotificationsList) {
7495 m_NotificationsList = new NotificationsList(this);
7496
7497 // calculate best size for Notification list
7498
7499 wxPoint ClientUpperRight = ClientToScreen(wxPoint(GetSize().x, 0));
7500 wxPoint list_bottom = ClientToScreen(wxPoint(0, GetSize().y / 2));
7501 int size_y = list_bottom.y - (ClientUpperRight.y + 5);
7502 size_y -= GetCharHeight();
7503 size_y = wxMax(size_y, 200); // ensure always big enough to see
7504
7505 m_NotificationsList->SetSize(wxSize(GetCharWidth() * 80, size_y));
7506
7507 wxPoint m_currentNLPos = ClientToScreen(wxPoint(
7508 GetSize().x / 2, m_notification_button->GetRect().y +
7509 m_notification_button->GetRect().height + 5));
7510
7511 m_NotificationsList->Move(m_currentNLPos);
7512 m_NotificationsList->Hide();
7513 }
7514
7515 if (m_NotificationsList->IsShown()) {
7516 m_NotificationsList->Hide();
7517 } else {
7518 m_NotificationsList->ReloadNotificationList();
7519 m_NotificationsList->Show();
7520 }
7521}
7522bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7523 if (!g_bShowChartBar) return false;
7524
7525 if (!m_Piano->MouseEvent(event)) return false;
7526
7527 cursor_region = CENTER;
7528 if (!g_btouch) SetCanvasCursor(event);
7529 return true;
7530}
7531
7532bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7533 if (!IsPrimaryCanvas()) return false;
7534
7535 if (g_MainToolbar) {
7536 if (!g_MainToolbar->MouseEvent(event))
7537 return false;
7538 else
7539 g_MainToolbar->RefreshToolbar();
7540 }
7541
7542 cursor_region = CENTER;
7543 if (!g_btouch) SetCanvasCursor(event);
7544 return true;
7545}
7546
7547bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7548 if (!IsPrimaryCanvas()) return false;
7549
7550 if (g_iENCToolbar) {
7551 if (!g_iENCToolbar->MouseEvent(event))
7552 return false;
7553 else {
7554 g_iENCToolbar->RefreshToolbar();
7555 return true;
7556 }
7557 }
7558 return false;
7559}
7560
7561bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7562 if (m_muiBar) {
7563 if (!m_muiBar->MouseEvent(event)) return false;
7564 }
7565
7566 cursor_region = CENTER;
7567 if (!g_btouch) SetCanvasCursor(event);
7568 if (m_muiBar)
7569 return true;
7570 else
7571 return false;
7572}
7573
7574bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7575 int x, y;
7576
7577 bool bret = false;
7578
7579 event.GetPosition(&x, &y);
7580
7581 x *= m_displayScale;
7582 y *= m_displayScale;
7583
7584 m_MouseDragging = event.Dragging();
7585
7586 // Some systems produce null drag events, where the pointer position has not
7587 // changed from the previous value. Detect this case, and abort further
7588 // processing (FS#1748)
7589#ifdef __WXMSW__
7590 if (event.Dragging()) {
7591 if ((x == mouse_x) && (y == mouse_y)) return true;
7592 }
7593#endif
7594
7595 mouse_x = x;
7596 mouse_y = y;
7597 mouse_leftisdown = event.LeftDown();
7599
7600 // Establish the event region
7601 cursor_region = CENTER;
7602
7603 int chartbar_height = GetChartbarHeight();
7604
7605 if (m_Compass && m_Compass->IsShown() &&
7606 m_Compass->GetRect().Contains(event.GetPosition())) {
7607 cursor_region = CENTER;
7608 } else if (x > xr_margin) {
7609 cursor_region = MID_RIGHT;
7610 } else if (x < xl_margin) {
7611 cursor_region = MID_LEFT;
7612 } else if (y > yb_margin - chartbar_height &&
7613 y < m_canvas_height - chartbar_height) {
7614 cursor_region = MID_TOP;
7615 } else if (y < yt_margin) {
7616 cursor_region = MID_BOT;
7617 } else {
7618 cursor_region = CENTER;
7619 }
7620
7621 if (!g_btouch) SetCanvasCursor(event);
7622
7623 // Protect from leftUp's coming from event handlers in child
7624 // windows who return focus to the canvas.
7625 leftIsDown = event.LeftDown();
7626
7627#ifndef __WXOSX__
7628 if (event.LeftDown()) {
7629 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7630 // The menu bar is temporarily visible due to alt having been pressed.
7631 // Clicking will hide it, and do nothing else.
7632 g_bTempShowMenuBar = false;
7633 parent_frame->ApplyGlobalSettings(false);
7634 return (true);
7635 }
7636 }
7637#endif
7638
7639 // Update modifiers here; some window managers never send the key event
7640 m_modkeys = 0;
7641 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7642 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7643
7644#ifdef __WXMSW__
7645 // TODO Test carefully in other platforms, remove ifdef....
7646 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7647 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7648#endif
7649
7650 event.SetEventObject(this);
7651 if (SendMouseEventToPlugins(event))
7652 return (true); // PlugIn did something, and does not want the canvas to
7653 // do anything else
7654
7655 // Capture LeftUp's and time them, unless it already came from the timer.
7656
7657 // Detect end of chart dragging
7658 if (g_btouch && m_bChartDragging && event.LeftUp()) {
7659 StartChartDragInertia();
7660 }
7661
7662 if (b_handle_dclick && event.LeftUp() && !singleClickEventIsValid) {
7663 // Ignore the second LeftUp after the DClick.
7664 if (m_DoubleClickTimer->IsRunning()) {
7665 m_DoubleClickTimer->Stop();
7666 return (true);
7667 }
7668
7669 // Save the event for later running if there is no DClick.
7670 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7671 singleClickEvent = event;
7672 singleClickEventIsValid = true;
7673 return (true);
7674 }
7675
7676 // This logic is necessary on MSW to handle the case where
7677 // a context (right-click) menu is dismissed without action
7678 // by clicking on the chart surface.
7679 // We need to avoid an unintentional pan by eating some clicks...
7680#ifdef __WXMSW__
7681 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7682 if (g_click_stop > 0) {
7683 g_click_stop--;
7684 return (true);
7685 }
7686 }
7687#endif
7688
7689 // Kick off the Rotation control timer
7690 if (GetUpMode() == COURSE_UP_MODE) {
7691 m_b_rot_hidef = false;
7692 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7693 } else
7694 pRotDefTimer->Stop();
7695
7696 // Retrigger the route leg / AIS target popup timer
7697 bool bRoll = !g_btouch;
7698#ifdef __ANDROID__
7699 bRoll = g_bRollover;
7700#endif
7701 if (bRoll) {
7702 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7703 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7704 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7705 m_RolloverPopupTimer.Start(
7706 10,
7707 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7708 else
7709 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7710 }
7711
7712 // Retrigger the cursor tracking timer
7713 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7714
7715// Show cursor position on Status Bar, if present
7716// except for GTK, under which status bar updates are very slow
7717// due to Update() call.
7718// In this case, as a workaround, update the status window
7719// after an interval timer (pCurTrackTimer) pops, which will happen
7720// whenever the mouse has stopped moving for specified interval.
7721// See the method OnCursorTrackTimerEvent()
7722#if !defined(__WXGTK__) && !defined(__WXQT__)
7723 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7724#endif
7725
7726 // Send the current cursor lat/lon to all PlugIns requesting it
7727 if (g_pi_manager) {
7728 // Occasionally, MSW will produce nonsense events on right click....
7729 // This results in an error in cursor geo position, so we skip this case
7730 if ((x >= 0) && (y >= 0))
7731 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7732 }
7733
7734 if (!g_btouch) {
7735 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7736 wxPoint p = ClientToScreen(wxPoint(x, y));
7737 }
7738 }
7739
7740 if (1 ) {
7741 // Route Creation Rubber Banding
7742 if (m_routeState >= 2) {
7743 r_rband.x = x;
7744 r_rband.y = y;
7745 m_bDrawingRoute = true;
7746
7747 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7748 Refresh(false);
7749 }
7750
7751 // Measure Tool Rubber Banding
7752 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7753 r_rband.x = x;
7754 r_rband.y = y;
7755 m_bDrawingRoute = true;
7756
7757 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7758 Refresh(false);
7759 }
7760 }
7761 return bret;
7762}
7763
7764void ChartCanvas::CallPopupMenu(int x, int y) {
7765 int mx, my;
7766 mx = x;
7767 my = y;
7768
7769 last_drag.x = mx;
7770 last_drag.y = my;
7771 if (m_routeState) { // creating route?
7772 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
7773 return;
7774 }
7775 // General Right Click
7776 // Look for selectable objects
7777 double slat, slon;
7778 slat = m_cursor_lat;
7779 slon = m_cursor_lon;
7780
7781#if defined(__WXMAC__) || defined(__ANDROID__)
7782 wxScreenDC sdc;
7783 ocpnDC dc(sdc);
7784#else
7785 wxClientDC cdc(GetParent());
7786 ocpnDC dc(cdc);
7787#endif
7788
7789 SelectItem *pFindAIS;
7790 SelectItem *pFindRP;
7791 SelectItem *pFindRouteSeg;
7792 SelectItem *pFindTrackSeg;
7793 SelectItem *pFindCurrent = NULL;
7794 SelectItem *pFindTide = NULL;
7795
7796 // Deselect any current objects
7797 if (m_pSelectedRoute) {
7798 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7799 m_pSelectedRoute->DeSelectRoute();
7800#ifdef ocpnUSE_GL
7801 if (g_bopengl && m_glcc) {
7802 InvalidateGL();
7803 Update();
7804 } else
7805#endif
7806 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7807 }
7808
7809 if (m_pFoundRoutePoint) {
7810 m_pFoundRoutePoint->m_bPtIsSelected = false;
7811 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
7812 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
7813 }
7814
7817 if (g_btouch && m_pRoutePointEditTarget) {
7818 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7819 m_pRoutePointEditTarget->m_bPtIsSelected = false;
7820 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7821 }
7822
7823 // Get all the selectable things at the cursor
7824 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7825 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7826 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7827 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7828 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7829
7830 if (m_bShowCurrent)
7831 pFindCurrent =
7832 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7833
7834 if (m_bShowTide) // look for tide stations
7835 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7836
7837 int seltype = 0;
7838
7839 // Try for AIS targets first
7840 if (pFindAIS) {
7841 m_FoundAIS_MMSI = pFindAIS->GetUserData();
7842
7843 // Make sure the target data is available
7844 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
7845 seltype |= SELTYPE_AISTARGET;
7846 }
7847
7848 // Now the various Route Parts
7849
7850 m_pFoundRoutePoint = NULL;
7851 if (pFindRP) {
7852 RoutePoint *pFirstVizPoint = NULL;
7853 RoutePoint *pFoundActiveRoutePoint = NULL;
7854 RoutePoint *pFoundVizRoutePoint = NULL;
7855 Route *pSelectedActiveRoute = NULL;
7856 Route *pSelectedVizRoute = NULL;
7857
7858 // There is at least one routepoint, so get the whole list
7859 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7860 SelectableItemList SelList =
7861 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7862 wxSelectableItemListNode *node = SelList.GetFirst();
7863 while (node) {
7864 SelectItem *pFindSel = node->GetData();
7865
7866 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7867
7868 // Get an array of all routes using this point
7869 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7870
7871 // Use route array (if any) to determine actual visibility for this point
7872 bool brp_viz = false;
7873 if (proute_array) {
7874 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7875 Route *pr = (Route *)proute_array->Item(ir);
7876 if (pr->IsVisible()) {
7877 brp_viz = true;
7878 break;
7879 }
7880 }
7881 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7882 // but still exists as a waypoint
7883 brp_viz = prp->IsVisible(); // so treat as isolated point
7884
7885 } else
7886 brp_viz = prp->IsVisible(); // isolated point
7887
7888 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7889
7890 // Use route array to choose the appropriate route
7891 // Give preference to any active route, otherwise select the first visible
7892 // route in the array for this point
7893 m_pSelectedRoute = NULL;
7894 if (proute_array) {
7895 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7896 Route *pr = (Route *)proute_array->Item(ir);
7897 if (pr->m_bRtIsActive) {
7898 pSelectedActiveRoute = pr;
7899 pFoundActiveRoutePoint = prp;
7900 break;
7901 }
7902 }
7903
7904 if (NULL == pSelectedVizRoute) {
7905 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7906 Route *pr = (Route *)proute_array->Item(ir);
7907 if (pr->IsVisible()) {
7908 pSelectedVizRoute = pr;
7909 pFoundVizRoutePoint = prp;
7910 break;
7911 }
7912 }
7913 }
7914
7915 delete proute_array;
7916 }
7917
7918 node = node->GetNext();
7919 }
7920
7921 // Now choose the "best" selections
7922 if (pFoundActiveRoutePoint) {
7923 m_pFoundRoutePoint = pFoundActiveRoutePoint;
7924 m_pSelectedRoute = pSelectedActiveRoute;
7925 } else if (pFoundVizRoutePoint) {
7926 m_pFoundRoutePoint = pFoundVizRoutePoint;
7927 m_pSelectedRoute = pSelectedVizRoute;
7928 } else
7929 // default is first visible point in list
7930 m_pFoundRoutePoint = pFirstVizPoint;
7931
7932 if (m_pSelectedRoute) {
7933 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7934 } else if (m_pFoundRoutePoint)
7935 seltype |= SELTYPE_MARKPOINT;
7936
7937 // Highlite the selected point, to verify the proper right click
7938 // selection
7939 if (m_pFoundRoutePoint) {
7940 m_pFoundRoutePoint->m_bPtIsSelected = true;
7941 wxRect wp_rect;
7942 RoutePointGui(*m_pFoundRoutePoint)
7943 .CalculateDCRect(m_dc_route, this, &wp_rect);
7944 RefreshRect(wp_rect, true);
7945 }
7946 }
7947
7948 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7949 // routes But call the popup handler with identifier appropriate to the type
7950 if (pFindRouteSeg) // there is at least one select item
7951 {
7952 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7953 SelectableItemList SelList =
7954 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7955
7956 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
7957 {
7958 // Choose the first visible route containing segment in the list
7959 wxSelectableItemListNode *node = SelList.GetFirst();
7960 while (node) {
7961 SelectItem *pFindSel = node->GetData();
7962
7963 Route *pr = (Route *)pFindSel->m_pData3;
7964 if (pr->IsVisible()) {
7965 m_pSelectedRoute = pr;
7966 break;
7967 }
7968 node = node->GetNext();
7969 }
7970 }
7971
7972 if (m_pSelectedRoute) {
7973 if (NULL == m_pFoundRoutePoint)
7974 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7975
7976 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7977 if (m_pSelectedRoute->m_bRtIsSelected) {
7978#ifdef ocpnUSE_GL
7979 if (g_bopengl && m_glcc) {
7980 InvalidateGL();
7981 Update();
7982 } else
7983#endif
7984 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7985 }
7986
7987 seltype |= SELTYPE_ROUTESEGMENT;
7988 }
7989 }
7990
7991 if (pFindTrackSeg) {
7992 m_pSelectedTrack = NULL;
7993 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7994 SelectableItemList SelList =
7995 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7996
7997 // Choose the first visible track containing segment in the list
7998 wxSelectableItemListNode *node = SelList.GetFirst();
7999 while (node) {
8000 SelectItem *pFindSel = node->GetData();
8001
8002 Track *pt = (Track *)pFindSel->m_pData3;
8003 if (pt->IsVisible()) {
8004 m_pSelectedTrack = pt;
8005 break;
8006 }
8007 node = node->GetNext();
8008 }
8009
8010 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8011 }
8012
8013 bool bseltc = false;
8014 // if(0 == seltype)
8015 {
8016 if (pFindCurrent) {
8017 // There may be multiple current entries at the same point.
8018 // For example, there often is a current substation (with directions
8019 // specified) co-located with its master. We want to select the
8020 // substation, so that the direction will be properly indicated on the
8021 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8022 // substation)
8023 IDX_entry *pIDX_best_candidate;
8024
8025 SelectItem *pFind = NULL;
8026 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8027 SelectableItemList SelList = pSelectTC->FindSelectionList(
8028 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_CURRENTPOINT);
8029
8030 // Default is first entry
8031 wxSelectableItemListNode *node = SelList.GetFirst();
8032 pFind = node->GetData();
8033 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8034
8035 if (SelList.GetCount() > 1) {
8036 node = node->GetNext();
8037 while (node) {
8038 pFind = node->GetData();
8039 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8040 if (pIDX_candidate->IDX_type == 'c') {
8041 pIDX_best_candidate = pIDX_candidate;
8042 break;
8043 }
8044
8045 node = node->GetNext();
8046 } // while (node)
8047 } else {
8048 wxSelectableItemListNode *node = SelList.GetFirst();
8049 pFind = node->GetData();
8050 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8051 }
8052
8053 m_pIDXCandidate = pIDX_best_candidate;
8054
8055 if (0 == seltype) {
8056 DrawTCWindow(x, y, (void *)pIDX_best_candidate);
8057 Refresh(false);
8058 bseltc = true;
8059 } else
8060 seltype |= SELTYPE_CURRENTPOINT;
8061 }
8062
8063 else if (pFindTide) {
8064 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8065
8066 if (0 == seltype) {
8067 DrawTCWindow(x, y, (void *)pFindTide->m_pData1);
8068 Refresh(false);
8069 bseltc = true;
8070 } else
8071 seltype |= SELTYPE_TIDEPOINT;
8072 }
8073 }
8074
8075 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8076
8077 if (!bseltc) {
8078 InvokeCanvasMenu(x, y, seltype);
8079
8080 // Clean up if not deleted in InvokeCanvasMenu
8081 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8082 m_pSelectedRoute->m_bRtIsSelected = false;
8083 }
8084
8085 m_pSelectedRoute = NULL;
8086
8087 if (m_pFoundRoutePoint) {
8088 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8089 m_pFoundRoutePoint->m_bPtIsSelected = false;
8090 }
8091 m_pFoundRoutePoint = NULL;
8092
8093 Refresh(true);
8094 }
8095
8096 // Seth: Is this refresh needed?
8097 Refresh(false); // needed for MSW, not GTK Why??
8098}
8099bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8100 // For now just bail out completely if the point clicked is not on the chart
8101 if (std::isnan(m_cursor_lat)) return false;
8102
8103 // Mouse Clicks
8104 bool ret = false; // return true if processed
8105
8106 int x, y, mx, my;
8107 event.GetPosition(&x, &y);
8108 mx = x;
8109 my = y;
8110
8111 // Calculate meaningful SelectRadius
8112 float SelectRadius;
8113 SelectRadius = g_Platform->GetSelectRadiusPix() /
8114 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8115
8117 // We start with Double Click processing. The first left click just starts a
8118 // timer and is remembered, then we actually do something if there is a
8119 // LeftDClick. If there is, the two single clicks are ignored.
8120
8121 if (event.LeftDClick() && (cursor_region == CENTER)) {
8122 m_DoubleClickTimer->Start();
8123 singleClickEventIsValid = false;
8124
8125 double zlat, zlon;
8126 GetCanvasPixPoint(x * g_current_monitor_dip_px_ratio,
8127 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8128
8129 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8130 if (m_bShowAIS) {
8131 SelectItem *pFindAIS;
8132 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8133
8134 if (pFindAIS) {
8135 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8136 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8137 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8138 }
8139 return true;
8140 }
8141 }
8142
8143 SelectableItemList rpSelList =
8144 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8145 wxSelectableItemListNode *node = rpSelList.GetFirst();
8146 bool b_onRPtarget = false;
8147 while (node) {
8148 SelectItem *pFind = node->GetData();
8149 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8150 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8151 b_onRPtarget = true;
8152 break;
8153 }
8154 node = node->GetNext();
8155 }
8156
8157 // Double tap with selected RoutePoint or Mark
8158
8159 if (m_pRoutePointEditTarget) {
8160 if (b_onRPtarget) {
8161 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8162 return true;
8163 } else {
8164 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8165 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8166 if (g_btouch)
8167 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8168 wxRect wp_rect;
8169 RoutePointGui(*m_pRoutePointEditTarget)
8170 .CalculateDCRect(m_dc_route, this, &wp_rect);
8171 m_pRoutePointEditTarget = NULL; // cancel selection
8172 RefreshRect(wp_rect, true);
8173 return true;
8174 }
8175 } else {
8176 node = rpSelList.GetFirst();
8177 if (node) {
8178 SelectItem *pFind = node->GetData();
8179 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8180 if (frp) {
8181 wxArrayPtrVoid *proute_array =
8182 g_pRouteMan->GetRouteArrayContaining(frp);
8183
8184 // Use route array (if any) to determine actual visibility for this
8185 // point
8186 bool brp_viz = false;
8187 if (proute_array) {
8188 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8189 Route *pr = (Route *)proute_array->Item(ir);
8190 if (pr->IsVisible()) {
8191 brp_viz = true;
8192 break;
8193 }
8194 }
8195 delete proute_array;
8196 if (!brp_viz &&
8197 frp->IsShared()) // is not visible as part of route, but still
8198 // exists as a waypoint
8199 brp_viz = frp->IsVisible(); // so treat as isolated point
8200 } else
8201 brp_viz = frp->IsVisible(); // isolated point
8202
8203 if (brp_viz) {
8204 ShowMarkPropertiesDialog(frp);
8205 return true;
8206 }
8207 }
8208 }
8209 }
8210
8211 SelectItem *cursorItem;
8212 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8213
8214 if (cursorItem) {
8215 Route *pr = (Route *)cursorItem->m_pData3;
8216 if (pr->IsVisible()) {
8217 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8218 return true;
8219 }
8220 }
8221
8222 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8223
8224 if (cursorItem) {
8225 Track *pt = (Track *)cursorItem->m_pData3;
8226 if (pt->IsVisible()) {
8227 ShowTrackPropertiesDialog(pt);
8228 return true;
8229 }
8230 }
8231
8232 // Found no object to act on, so show chart info.
8233
8234 ShowObjectQueryWindow(x, y, zlat, zlon);
8235 return true;
8236 }
8237
8239 if (event.LeftDown()) {
8240 // This really should not be needed, but....
8241 // on Windows, when using wxAUIManager, sometimes the focus is lost
8242 // when clicking into another pane, e.g.the AIS target list, and then back
8243 // to this pane. Oddly, some mouse events are not lost, however. Like this
8244 // one....
8245 SetFocus();
8246
8247 last_drag.x = mx;
8248 last_drag.y = my;
8249 leftIsDown = true;
8250
8251 if (!g_btouch) {
8252 if (m_routeState) // creating route?
8253 {
8254 double rlat, rlon;
8255 bool appending = false;
8256 bool inserting = false;
8257 Route *tail = 0;
8258
8259 SetCursor(*pCursorPencil);
8260 rlat = m_cursor_lat;
8261 rlon = m_cursor_lon;
8262
8263 m_bRouteEditing = true;
8264
8265 if (m_routeState == 1) {
8266 m_pMouseRoute = new Route();
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 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8415
8416 if (m_routeState > 1)
8417 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8418 Undo_IsOrphanded, NULL);
8419 }
8420
8421 if (m_pMouseRoute) {
8422 if (m_routeState == 1) {
8423 // First point in the route.
8424 m_pMouseRoute->AddPoint(pMousePoint);
8425 } else {
8426 if (m_pMouseRoute->m_NextLegGreatCircle) {
8427 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8428 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8429 &rhumbBearing, &rhumbDist);
8430 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8431 rlat, &gcDist, &gcBearing, NULL);
8432 double gcDistNM = gcDist / 1852.0;
8433
8434 // Empirically found expression to get reasonable route segments.
8435 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8436 pow(rhumbDist - gcDistNM - 1, 0.5);
8437
8438 wxString msg;
8439 msg << _("For this leg the Great Circle route is ")
8440 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8441 << _(" shorter than rhumbline.\n\n")
8442 << _("Would you like include the Great Circle routing points "
8443 "for this leg?");
8444
8445 m_FinishRouteOnKillFocus = false;
8446 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8447 // does not fully capture mouse
8448
8449 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8450 wxYES_NO | wxNO_DEFAULT);
8451
8452 m_disable_edge_pan = false;
8453 m_FinishRouteOnKillFocus = true;
8454
8455 if (answer == wxID_YES) {
8456 RoutePoint *gcPoint;
8457 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8458 wxRealPoint gcCoord;
8459
8460 for (int i = 1; i <= segmentCount; i++) {
8461 double fraction = (double)i * (1.0 / (double)segmentCount);
8462 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8463 gcDist * fraction, gcBearing,
8464 &gcCoord.x, &gcCoord.y, NULL);
8465
8466 if (i < segmentCount) {
8467 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
8468 _T(""), wxEmptyString);
8469 gcPoint->SetNameShown(false);
8470 pConfig->AddNewWayPoint(gcPoint, -1);
8471 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8472 gcPoint);
8473 } else {
8474 gcPoint = pMousePoint; // Last point, previously exsisting!
8475 }
8476
8477 m_pMouseRoute->AddPoint(gcPoint);
8478 pSelect->AddSelectableRouteSegment(
8479 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8480 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8481 prevGcPoint = gcPoint;
8482 }
8483
8484 undo->CancelUndoableAction(true);
8485
8486 } else {
8487 m_pMouseRoute->AddPoint(pMousePoint);
8488 pSelect->AddSelectableRouteSegment(
8489 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8490 pMousePoint, m_pMouseRoute);
8491 undo->AfterUndoableAction(m_pMouseRoute);
8492 }
8493 } else {
8494 // Ordinary rhumblinesegment.
8495 m_pMouseRoute->AddPoint(pMousePoint);
8496 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8497 rlon, m_prev_pMousePoint,
8498 pMousePoint, m_pMouseRoute);
8499 undo->AfterUndoableAction(m_pMouseRoute);
8500 }
8501 }
8502 }
8503 m_prev_rlat = rlat;
8504 m_prev_rlon = rlon;
8505 m_prev_pMousePoint = pMousePoint;
8506 if (m_pMouseRoute)
8507 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8508
8509 m_routeState++;
8510
8511 if (appending ||
8512 inserting) { // Appending a route or making a new route
8513 int connect = tail->GetIndexOf(pMousePoint);
8514 if (connect == 1) {
8515 inserting = false; // there is nothing to insert
8516 appending = true; // so append
8517 }
8518 int length = tail->GetnPoints();
8519
8520 int i;
8521 int start, stop;
8522 if (appending) {
8523 start = connect + 1;
8524 stop = length;
8525 } else { // inserting
8526 start = 1;
8527 stop = connect;
8528 m_pMouseRoute->RemovePoint(
8529 m_pMouseRoute
8530 ->GetLastPoint()); // Remove the first and only point
8531 }
8532 for (i = start; i <= stop; i++) {
8533 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8534 if (m_pMouseRoute)
8535 m_pMouseRoute->m_lastMousePointIndex =
8536 m_pMouseRoute->GetnPoints();
8537 m_routeState++;
8538 gFrame->RefreshAllCanvas();
8539 ret = true;
8540 }
8541 m_prev_rlat =
8542 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8543 m_prev_rlon =
8544 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8545 m_pMouseRoute->FinalizeForRendering();
8546 }
8547 gFrame->RefreshAllCanvas();
8548 ret = true;
8549 }
8550
8551 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8552 {
8553 SetCursor(*pCursorPencil);
8554
8555 if (!m_pMeasureRoute) {
8556 m_pMeasureRoute = new Route();
8557 pRouteList->Append(m_pMeasureRoute);
8558 }
8559
8560 if (m_nMeasureState == 1) {
8561 r_rband.x = x;
8562 r_rband.y = y;
8563 }
8564
8565 RoutePoint *pMousePoint = new RoutePoint(m_cursor_lat, m_cursor_lon,
8566 wxString(_T ( "circle" )),
8567 wxEmptyString, wxEmptyString);
8568 pMousePoint->m_bShowName = false;
8569 pMousePoint->SetShowWaypointRangeRings(false);
8570
8571 m_pMeasureRoute->AddPoint(pMousePoint);
8572
8573 m_prev_rlat = m_cursor_lat;
8574 m_prev_rlon = m_cursor_lon;
8575 m_prev_pMousePoint = pMousePoint;
8576 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8577
8578 m_nMeasureState++;
8579 gFrame->RefreshAllCanvas();
8580 ret = true;
8581 }
8582
8583 else {
8584 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8585 }
8586 } // !g_btouch
8587 else { // g_btouch
8588
8589 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8590 // if near screen edge, pan with injection
8591 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8592 // return;
8593 // }
8594 }
8595 }
8596
8597 if (ret) return true;
8598 }
8599
8600 if (event.Dragging()) {
8601 // in touch screen mode ensure the finger/cursor is on the selected point's
8602 // radius to allow dragging
8603 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8604 if (g_btouch) {
8605 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8606 SelectItem *pFind = NULL;
8607 SelectableItemList SelList = pSelect->FindSelectionList(
8608 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8609 wxSelectableItemListNode *node = SelList.GetFirst();
8610 while (node) {
8611 pFind = node->GetData();
8612 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8613 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8614 node = node->GetNext();
8615 }
8616 }
8617
8618 // Check for use of dragHandle
8619 if (m_pRoutePointEditTarget &&
8620 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8621 SelectItem *pFind = NULL;
8622 SelectableItemList SelList = pSelect->FindSelectionList(
8623 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8624 wxSelectableItemListNode *node = SelList.GetFirst();
8625 while (node) {
8626 pFind = node->GetData();
8627 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8628 if (m_pRoutePointEditTarget == frp) {
8629 m_bIsInRadius = true;
8630 break;
8631 }
8632 node = node->GetNext();
8633 }
8634
8635 if (!m_dragoffsetSet) {
8636 RoutePointGui(*m_pRoutePointEditTarget)
8637 .PresetDragOffset(this, mouse_x, mouse_y);
8638 m_dragoffsetSet = true;
8639 }
8640 }
8641 }
8642
8643 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8644 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8645
8646 if (NULL == g_pMarkInfoDialog) {
8647 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8648 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8649 DraggingAllowed = false;
8650
8651 if (m_pRoutePointEditTarget &&
8652 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8653 DraggingAllowed = false;
8654
8655 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8656
8657 if (DraggingAllowed) {
8658 if (!undo->InUndoableAction()) {
8659 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8660 Undo_NeedsCopy, m_pFoundPoint);
8661 }
8662
8663 // Get the update rectangle for the union of the un-edited routes
8664 wxRect pre_rect;
8665
8666 if (!g_bopengl && m_pEditRouteArray) {
8667 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8668 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8669 // Need to validate route pointer
8670 // Route may be gone due to drgging close to ownship with
8671 // "Delete On Arrival" state set, as in the case of
8672 // navigating to an isolated waypoint on a temporary route
8673 if (g_pRouteMan->IsRouteValid(pr)) {
8674 wxRect route_rect;
8675 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8676 pre_rect.Union(route_rect);
8677 }
8678 }
8679 }
8680
8681 double new_cursor_lat = m_cursor_lat;
8682 double new_cursor_lon = m_cursor_lon;
8683
8684 if (CheckEdgePan(x, y, true, 5, 2))
8685 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8686
8687 // update the point itself
8688 if (g_btouch) {
8689 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8690 // new_cursor_lat, new_cursor_lon);
8691 RoutePointGui(*m_pRoutePointEditTarget)
8692 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8693 // update the Drag Handle entry in the pSelect list
8694 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8695 m_pRoutePointEditTarget,
8696 SELTYPE_DRAGHANDLE);
8697 m_pFoundPoint->m_slat =
8698 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8699 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8700 } else {
8701 m_pRoutePointEditTarget->m_lat =
8702 new_cursor_lat; // update the RoutePoint entry
8703 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8704 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8705 m_pFoundPoint->m_slat =
8706 new_cursor_lat; // update the SelectList entry
8707 m_pFoundPoint->m_slon = new_cursor_lon;
8708 }
8709
8710 // Update the MarkProperties Dialog, if currently shown
8711 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8712 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8713 g_pMarkInfoDialog->UpdateProperties(true);
8714 }
8715
8716 if (g_bopengl) {
8717 // InvalidateGL();
8718 Refresh(false);
8719 } else {
8720 // Get the update rectangle for the edited route
8721 wxRect post_rect;
8722
8723 if (m_pEditRouteArray) {
8724 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8725 ir++) {
8726 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8727 if (g_pRouteMan->IsRouteValid(pr)) {
8728 wxRect route_rect;
8729 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8730 post_rect.Union(route_rect);
8731 }
8732 }
8733 }
8734
8735 // Invalidate the union region
8736 pre_rect.Union(post_rect);
8737 RefreshRect(pre_rect, false);
8738 }
8739 gFrame->RefreshCanvasOther(this);
8740 m_bRoutePoinDragging = true;
8741 }
8742 ret = true;
8743 } // if Route Editing
8744
8745 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8746 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8747
8748 if (NULL == g_pMarkInfoDialog) {
8749 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8750 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8751 DraggingAllowed = false;
8752
8753 if (m_pRoutePointEditTarget &&
8754 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8755 DraggingAllowed = false;
8756
8757 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8758
8759 if (DraggingAllowed) {
8760 if (!undo->InUndoableAction()) {
8761 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8762 Undo_NeedsCopy, m_pFoundPoint);
8763 }
8764
8765 // The mark may be an anchorwatch
8766 double lpp1 = 0.;
8767 double lpp2 = 0.;
8768 double lppmax;
8769
8770 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8771 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8772 }
8773 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8774 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8775 }
8776 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8777
8778 // Get the update rectangle for the un-edited mark
8779 wxRect pre_rect;
8780 if (!g_bopengl) {
8781 RoutePointGui(*m_pRoutePointEditTarget)
8782 .CalculateDCRect(m_dc_route, this, &pre_rect);
8783 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8784 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8785 (int)(lppmax - (pre_rect.height / 2)));
8786 }
8787
8788 // update the point itself
8789 if (g_btouch) {
8790 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8791 // m_cursor_lat, m_cursor_lon);
8792 RoutePointGui(*m_pRoutePointEditTarget)
8793 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8794 // update the Drag Handle entry in the pSelect list
8795 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
8796 m_pRoutePointEditTarget,
8797 SELTYPE_DRAGHANDLE);
8798 m_pFoundPoint->m_slat =
8799 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8800 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8801 } else {
8802 m_pRoutePointEditTarget->m_lat =
8803 m_cursor_lat; // update the RoutePoint entry
8804 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
8805 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8806 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
8807 m_pFoundPoint->m_slon = m_cursor_lon;
8808 }
8809
8810 // Update the MarkProperties Dialog, if currently shown
8811 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8812 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8813 g_pMarkInfoDialog->UpdateProperties(true);
8814 }
8815
8816 // Invalidate the union region
8817 if (g_bopengl) {
8818 if (!g_btouch) InvalidateGL();
8819 Refresh(false);
8820 } else {
8821 // Get the update rectangle for the edited mark
8822 wxRect post_rect;
8823 RoutePointGui(*m_pRoutePointEditTarget)
8824 .CalculateDCRect(m_dc_route, this, &post_rect);
8825 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
8826 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
8827 (int)(lppmax - (post_rect.height / 2)));
8828
8829 // Invalidate the union region
8830 pre_rect.Union(post_rect);
8831 RefreshRect(pre_rect, false);
8832 }
8833 gFrame->RefreshCanvasOther(this);
8834 m_bRoutePoinDragging = true;
8835 }
8836 ret = true;
8837 }
8838
8839 if (ret) return true;
8840 } // dragging
8841
8842 if (event.LeftUp()) {
8843 bool b_startedit_route = false;
8844 m_dragoffsetSet = false;
8845
8846 if (g_btouch) {
8847 m_bChartDragging = false;
8848 m_bIsInRadius = false;
8849
8850 if (m_routeState) // creating route?
8851 {
8852 if (m_bedge_pan) {
8853 m_bedge_pan = false;
8854 return false;
8855 }
8856
8857 double rlat, rlon;
8858 bool appending = false;
8859 bool inserting = false;
8860 Route *tail = 0;
8861
8862 rlat = m_cursor_lat;
8863 rlon = m_cursor_lon;
8864
8865 if (m_pRoutePointEditTarget) {
8866 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8867 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8868 if (!g_bopengl) {
8869 wxRect wp_rect;
8870 RoutePointGui(*m_pRoutePointEditTarget)
8871 .CalculateDCRect(m_dc_route, this, &wp_rect);
8872 RefreshRect(wp_rect, true);
8873 }
8874 m_pRoutePointEditTarget = NULL;
8875 }
8876 m_bRouteEditing = true;
8877
8878 if (m_routeState == 1) {
8879 m_pMouseRoute = new Route();
8880 m_pMouseRoute->SetHiLite(50);
8881 pRouteList->Append(m_pMouseRoute);
8882 r_rband.x = x;
8883 r_rband.y = y;
8884 }
8885
8886 // Check to see if there is a nearby point which may be reused
8887 RoutePoint *pMousePoint = NULL;
8888
8889 // Calculate meaningful SelectRadius
8890 double nearby_radius_meters =
8891 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8892
8893 RoutePoint *pNearbyPoint =
8894 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8895 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8896 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8897 int dlg_return;
8898#ifndef __WXOSX__
8899 m_FinishRouteOnKillFocus =
8900 false; // Avoid route finish on focus change for message dialog
8901 dlg_return = OCPNMessageBox(
8902 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
8903 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8904 m_FinishRouteOnKillFocus = true;
8905#else
8906 dlg_return = wxID_YES;
8907#endif
8908 if (dlg_return == wxID_YES) {
8909 pMousePoint = pNearbyPoint;
8910
8911 // Using existing waypoint, so nothing to delete for undo.
8912 if (m_routeState > 1)
8913 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8914 Undo_HasParent, NULL);
8915 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8916
8917 bool procede = false;
8918 if (tail) {
8919 procede = true;
8920 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8921 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8922 procede = false;
8923 }
8924
8925 if (procede) {
8926 int dlg_return;
8927 m_FinishRouteOnKillFocus = false;
8928 if (m_routeState == 1) { // first point in new route, preceeding
8929 // route to be added? touch case
8930
8931 wxString dmsg =
8932 _("Insert first part of this route in the new route?");
8933 if (tail->GetIndexOf(pMousePoint) ==
8934 tail->GetnPoints()) // Starting on last point of another
8935 // route?
8936 dmsg = _("Insert this route in the new route?");
8937
8938 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8939 dlg_return =
8940 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8941 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8942 m_FinishRouteOnKillFocus = true;
8943
8944 if (dlg_return == wxID_YES) {
8945 inserting = true; // part of the other route will be
8946 // preceeding the new route
8947 }
8948 }
8949 } else {
8950 wxString dmsg =
8951 _("Append last part of this route to the new route?");
8952 if (tail->GetIndexOf(pMousePoint) == 1)
8953 dmsg = _(
8954 "Append this route to the new route?"); // Picking the
8955 // first point of
8956 // another route?
8957
8958 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8959 dlg_return =
8960 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8961 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8962 m_FinishRouteOnKillFocus = true;
8963
8964 if (dlg_return == wxID_YES) {
8965 appending = true; // part of the other route will be
8966 // appended to the new route
8967 }
8968 }
8969 }
8970 }
8971
8972 // check all other routes to see if this point appears in any other
8973 // route If it appears in NO other route, then it should e
8974 // considered an isolated mark
8975 if (!FindRouteContainingWaypoint(pMousePoint))
8976 pMousePoint->SetShared(true);
8977 }
8978 }
8979
8980 if (NULL == pMousePoint) { // need a new point
8981 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8982 _T(""), wxEmptyString);
8983 pMousePoint->SetNameShown(false);
8984
8985 pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8986 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8987
8988 if (m_routeState > 1)
8989 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8990 Undo_IsOrphanded, NULL);
8991 }
8992
8993 if (m_routeState == 1) {
8994 // First point in the route.
8995 m_pMouseRoute->AddPoint(pMousePoint);
8996 } else {
8997 if (m_pMouseRoute->m_NextLegGreatCircle) {
8998 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8999 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9000 &rhumbBearing, &rhumbDist);
9001 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9002 &gcDist, &gcBearing, NULL);
9003 double gcDistNM = gcDist / 1852.0;
9004
9005 // Empirically found expression to get reasonable route segments.
9006 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9007 pow(rhumbDist - gcDistNM - 1, 0.5);
9008
9009 wxString msg;
9010 msg << _("For this leg the Great Circle route is ")
9011 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9012 << _(" shorter than rhumbline.\n\n")
9013 << _("Would you like include the Great Circle routing points "
9014 "for this leg?");
9015
9016#ifndef __WXOSX__
9017 m_FinishRouteOnKillFocus = false;
9018 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9019 wxYES_NO | wxNO_DEFAULT);
9020 m_FinishRouteOnKillFocus = true;
9021#else
9022 int answer = wxID_NO;
9023#endif
9024
9025 if (answer == wxID_YES) {
9026 RoutePoint *gcPoint;
9027 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9028 wxRealPoint gcCoord;
9029
9030 for (int i = 1; i <= segmentCount; i++) {
9031 double fraction = (double)i * (1.0 / (double)segmentCount);
9032 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9033 gcDist * fraction, gcBearing,
9034 &gcCoord.x, &gcCoord.y, NULL);
9035
9036 if (i < segmentCount) {
9037 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
9038 _T(""), wxEmptyString);
9039 gcPoint->SetNameShown(false);
9040 pConfig->AddNewWayPoint(gcPoint, -1);
9041 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9042 gcPoint);
9043 } else {
9044 gcPoint = pMousePoint; // Last point, previously exsisting!
9045 }
9046
9047 m_pMouseRoute->AddPoint(gcPoint);
9048 pSelect->AddSelectableRouteSegment(
9049 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9050 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9051 prevGcPoint = gcPoint;
9052 }
9053
9054 undo->CancelUndoableAction(true);
9055
9056 } else {
9057 m_pMouseRoute->AddPoint(pMousePoint);
9058 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9059 rlon, m_prev_pMousePoint,
9060 pMousePoint, m_pMouseRoute);
9061 undo->AfterUndoableAction(m_pMouseRoute);
9062 }
9063 } else {
9064 // Ordinary rhumblinesegment.
9065 m_pMouseRoute->AddPoint(pMousePoint);
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 }
9072
9073 m_prev_rlat = rlat;
9074 m_prev_rlon = rlon;
9075 m_prev_pMousePoint = pMousePoint;
9076 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9077
9078 m_routeState++;
9079
9080 if (appending ||
9081 inserting) { // Appending a route or making a new route
9082 int connect = tail->GetIndexOf(pMousePoint);
9083 if (connect == 1) {
9084 inserting = false; // there is nothing to insert
9085 appending = true; // so append
9086 }
9087 int length = tail->GetnPoints();
9088
9089 int i;
9090 int start, stop;
9091 if (appending) {
9092 start = connect + 1;
9093 stop = length;
9094 } else { // inserting
9095 start = 1;
9096 stop = connect;
9097 m_pMouseRoute->RemovePoint(
9098 m_pMouseRoute
9099 ->GetLastPoint()); // Remove the first and only point
9100 }
9101 for (i = start; i <= stop; i++) {
9102 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9103 if (m_pMouseRoute)
9104 m_pMouseRoute->m_lastMousePointIndex =
9105 m_pMouseRoute->GetnPoints();
9106 m_routeState++;
9107 gFrame->RefreshAllCanvas();
9108 ret = true;
9109 }
9110 m_prev_rlat =
9111 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9112 m_prev_rlon =
9113 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9114 m_pMouseRoute->FinalizeForRendering();
9115 }
9116
9117 Refresh(true);
9118 ret = true;
9119 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9120 {
9121 if (m_bedge_pan) {
9122 m_bedge_pan = false;
9123 return false;
9124 }
9125
9126 if (m_nMeasureState == 1) {
9127 m_pMeasureRoute = new Route();
9128 pRouteList->Append(m_pMeasureRoute);
9129 r_rband.x = x;
9130 r_rband.y = y;
9131 }
9132
9133 if (m_pMeasureRoute) {
9134 RoutePoint *pMousePoint = new RoutePoint(
9135 m_cursor_lat, m_cursor_lon, wxString(_T ( "circle" )),
9136 wxEmptyString, wxEmptyString);
9137 pMousePoint->m_bShowName = false;
9138
9139 m_pMeasureRoute->AddPoint(pMousePoint);
9140
9141 m_prev_rlat = m_cursor_lat;
9142 m_prev_rlon = m_cursor_lon;
9143 m_prev_pMousePoint = pMousePoint;
9144 m_pMeasureRoute->m_lastMousePointIndex =
9145 m_pMeasureRoute->GetnPoints();
9146
9147 m_nMeasureState++;
9148 } else {
9149 CancelMeasureRoute();
9150 }
9151
9152 Refresh(true);
9153 ret = true;
9154 } else {
9155 bool bSelectAllowed = true;
9156 if (NULL == g_pMarkInfoDialog) {
9157 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9158 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9159 bSelectAllowed = false;
9160
9161 /*if this left up happens at the end of a route point dragging and if
9162 the cursor/thumb is on the draghandle icon, not on the point iself a new
9163 selection will select nothing and the drag will never be ended, so the
9164 legs around this point never selectable. At this step we don't need a
9165 new selection, just keep the previoulsly selected and dragged point */
9166 if (m_bRoutePoinDragging) bSelectAllowed = false;
9167
9168 if (bSelectAllowed) {
9169 bool b_was_editing_mark = m_bMarkEditing;
9170 bool b_was_editing_route = m_bRouteEditing;
9171 FindRoutePointsAtCursor(SelectRadius,
9172 true); // Possibly selecting a point in a
9173 // route for later dragging
9174
9175 /*route and a mark points in layer can't be dragged so should't be
9176 * selected and no draghandle icon*/
9177 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9178 m_pRoutePointEditTarget = NULL;
9179
9180 if (!b_was_editing_route) {
9181 if (m_pEditRouteArray) {
9182 b_startedit_route = true;
9183
9184 // Hide the track and route rollover during route point edit, not
9185 // needed, and may be confusing
9186 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9187 m_pTrackRolloverWin->IsActive(false);
9188 }
9189 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9190 m_pRouteRolloverWin->IsActive(false);
9191 }
9192
9193 wxRect pre_rect;
9194 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9195 ir++) {
9196 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9197 // Need to validate route pointer
9198 // Route may be gone due to drgging close to ownship with
9199 // "Delete On Arrival" state set, as in the case of
9200 // navigating to an isolated waypoint on a temporary route
9201 if (g_pRouteMan->IsRouteValid(pr)) {
9202 // pr->SetHiLite(50);
9203 wxRect route_rect;
9204 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9205 pre_rect.Union(route_rect);
9206 }
9207 }
9208 RefreshRect(pre_rect, true);
9209 }
9210 } else {
9211 b_startedit_route = false;
9212 }
9213
9214 // Mark editing
9215 if (m_pRoutePointEditTarget) {
9216 if (b_was_editing_mark ||
9217 b_was_editing_route) { // kill previous hilight
9218 if (m_lastRoutePointEditTarget) {
9219 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9220 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9221 RoutePointGui(*m_lastRoutePointEditTarget)
9222 .EnableDragHandle(false);
9223 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9224 SELTYPE_DRAGHANDLE);
9225 }
9226 }
9227
9228 if (m_pRoutePointEditTarget) {
9229 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9230 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9231 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9232 wxPoint2DDouble dragHandlePoint =
9233 RoutePointGui(*m_pRoutePointEditTarget)
9234 .GetDragHandlePoint(this);
9235 pSelect->AddSelectablePoint(
9236 dragHandlePoint.m_y, dragHandlePoint.m_x,
9237 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9238 }
9239 } else { // Deselect everything
9240 if (m_lastRoutePointEditTarget) {
9241 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9242 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9243 RoutePointGui(*m_lastRoutePointEditTarget)
9244 .EnableDragHandle(false);
9245 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9246 SELTYPE_DRAGHANDLE);
9247
9248 // Clear any routes being edited, probably orphans
9249 wxArrayPtrVoid *lastEditRouteArray =
9250 g_pRouteMan->GetRouteArrayContaining(
9251 m_lastRoutePointEditTarget);
9252 if (lastEditRouteArray) {
9253 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9254 ir++) {
9255 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9256 if (g_pRouteMan->IsRouteValid(pr)) {
9257 pr->m_bIsBeingEdited = false;
9258 }
9259 }
9260 delete lastEditRouteArray;
9261 }
9262 }
9263 }
9264
9265 // Do the refresh
9266
9267 if (g_bopengl) {
9268 InvalidateGL();
9269 Refresh(false);
9270 } else {
9271 if (m_lastRoutePointEditTarget) {
9272 wxRect wp_rect;
9273 RoutePointGui(*m_lastRoutePointEditTarget)
9274 .CalculateDCRect(m_dc_route, this, &wp_rect);
9275 RefreshRect(wp_rect, true);
9276 }
9277
9278 if (m_pRoutePointEditTarget) {
9279 wxRect wp_rect;
9280 RoutePointGui(*m_pRoutePointEditTarget)
9281 .CalculateDCRect(m_dc_route, this, &wp_rect);
9282 RefreshRect(wp_rect, true);
9283 }
9284 }
9285 }
9286 } // bSelectAllowed
9287
9288 // Check to see if there is a route or AIS target under the cursor
9289 // If so, start the rollover timer which creates the popup
9290 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9291 bool b_start_rollover = false;
9292 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9293 SelectItem *pFind = pSelectAIS->FindSelection(
9294 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9295 if (pFind) b_start_rollover = true;
9296 }
9297
9298 if (!b_start_rollover && !b_startedit_route) {
9299 SelectableItemList SelList = pSelect->FindSelectionList(
9300 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9301 wxSelectableItemListNode *node = SelList.GetFirst();
9302 while (node) {
9303 SelectItem *pFindSel = node->GetData();
9304
9305 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9306
9307 if (pr && pr->IsVisible()) {
9308 b_start_rollover = true;
9309 break;
9310 }
9311 node = node->GetNext();
9312 } // while
9313 }
9314
9315 if (!b_start_rollover && !b_startedit_route) {
9316 SelectableItemList SelList = pSelect->FindSelectionList(
9317 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9318 wxSelectableItemListNode *node = SelList.GetFirst();
9319 while (node) {
9320 SelectItem *pFindSel = node->GetData();
9321
9322 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9323
9324 if (tr && tr->IsVisible()) {
9325 b_start_rollover = true;
9326 break;
9327 }
9328 node = node->GetNext();
9329 } // while
9330 }
9331
9332 if (b_start_rollover)
9333 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9334 wxTIMER_ONE_SHOT);
9335 Route *tail = 0;
9336 Route *current = 0;
9337 bool appending = false;
9338 bool inserting = false;
9339 int connect = 0;
9340 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9341 // drag
9342 if (m_pRoutePointEditTarget) {
9343 // Check to see if there is a nearby point which may replace the
9344 // dragged one
9345 RoutePoint *pMousePoint = NULL;
9346
9347 int index_last;
9348 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9349 double nearby_radius_meters =
9350 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9351 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9352 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9353 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9354 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9355 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9356 bool duplicate =
9357 false; // ensure we won't create duplicate point in routes
9358 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9359 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9360 ir++) {
9361 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9362 if (pr && pr->pRoutePointList) {
9363 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9364 wxNOT_FOUND) {
9365 duplicate = true;
9366 break;
9367 }
9368 }
9369 }
9370 }
9371
9372 // Special case:
9373 // Allow "re-use" of a route's waypoints iff it is a simple
9374 // isolated route. This allows, for instance, creation of a closed
9375 // polygon route
9376 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9377
9378 if (!duplicate) {
9379 int dlg_return;
9380 dlg_return =
9381 OCPNMessageBox(this,
9382 _("Replace this RoutePoint by the nearby "
9383 "Waypoint?"),
9384 _("OpenCPN RoutePoint change"),
9385 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9386 if (dlg_return == wxID_YES) {
9387 /*double confirmation if the dragged point has been manually
9388 * created which can be important and could be deleted
9389 * unintentionally*/
9390
9391 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9392 pNearbyPoint);
9393 current =
9394 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9395
9396 if (tail && current && (tail != current)) {
9397 int dlg_return1;
9398 connect = tail->GetIndexOf(pNearbyPoint);
9399 int index_current_route =
9400 current->GetIndexOf(m_pRoutePointEditTarget);
9401 index_last = current->GetIndexOf(current->GetLastPoint());
9402 dlg_return1 = wxID_NO;
9403 if (index_last ==
9404 index_current_route) { // we are dragging the last
9405 // point of the route
9406 if (connect != tail->GetnPoints()) { // anything to do?
9407
9408 wxString dmsg(
9409 _("Last part of route to be appended to dragged "
9410 "route?"));
9411 if (connect == 1)
9412 dmsg =
9413 _("Full route to be appended to dragged route?");
9414
9415 dlg_return1 = OCPNMessageBox(
9416 this, dmsg, _("OpenCPN Route Create"),
9417 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9418 if (dlg_return1 == wxID_YES) {
9419 appending = true;
9420 }
9421 }
9422 } else if (index_current_route ==
9423 1) { // dragging the first point of the route
9424 if (connect != 1) { // anything to do?
9425
9426 wxString dmsg(
9427 _("First part of route to be inserted into dragged "
9428 "route?"));
9429 if (connect == tail->GetnPoints())
9430 dmsg = _(
9431 "Full route to be inserted into dragged route?");
9432
9433 dlg_return1 = OCPNMessageBox(
9434 this, dmsg, _("OpenCPN Route Create"),
9435 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9436 if (dlg_return1 == wxID_YES) {
9437 inserting = true;
9438 }
9439 }
9440 }
9441 }
9442
9443 if (m_pRoutePointEditTarget->IsShared()) {
9444 // dlg_return = wxID_NO;
9445 dlg_return = OCPNMessageBox(
9446 this,
9447 _("Do you really want to delete and replace this "
9448 "WayPoint") +
9449 _T("\n") + _("which has been created manually?"),
9450 ("OpenCPN RoutePoint warning"),
9451 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9452 }
9453 }
9454 if (dlg_return == wxID_YES) {
9455 pMousePoint = pNearbyPoint;
9456 if (pMousePoint->m_bIsolatedMark) {
9457 pMousePoint->SetShared(true);
9458 }
9459 pMousePoint->m_bIsolatedMark =
9460 false; // definitely no longer isolated
9461 pMousePoint->m_bIsInRoute = true;
9462 }
9463 }
9464 }
9465 }
9466 if (!pMousePoint)
9467 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9468
9469 if (m_pEditRouteArray) {
9470 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9471 ir++) {
9472 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9473 if (g_pRouteMan->IsRouteValid(pr)) {
9474 if (pMousePoint) { // remove the dragged point and insert the
9475 // nearby
9476 int nRP =
9477 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9478
9479 pSelect->DeleteAllSelectableRoutePoints(pr);
9480 pSelect->DeleteAllSelectableRouteSegments(pr);
9481
9482 pr->pRoutePointList->Insert(nRP, pMousePoint);
9483 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9484
9485 pSelect->AddAllSelectableRouteSegments(pr);
9486 pSelect->AddAllSelectableRoutePoints(pr);
9487 }
9488 pr->FinalizeForRendering();
9489 pr->UpdateSegmentDistances();
9490 if (m_bRoutePoinDragging) pConfig->UpdateRoute(pr);
9491 }
9492 }
9493 }
9494
9495 // Update the RouteProperties Dialog, if currently shown
9496 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9497 if (m_pEditRouteArray) {
9498 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9499 ir++) {
9500 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9501 if (g_pRouteMan->IsRouteValid(pr)) {
9502 if (pRoutePropDialog->GetRoute() == pr) {
9503 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9504 }
9505 /* cannot edit track points anyway
9506 else if ( ( NULL !=
9507 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9508 pTrackPropDialog->m_pTrack == pr ) {
9509 pTrackPropDialog->SetTrackAndUpdate(
9510 pr );
9511 }
9512 */
9513 }
9514 }
9515 }
9516 }
9517 if (pMousePoint) { // clear all about the dragged point
9518 pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9519 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9520 // Hide mark properties dialog if open on the replaced point
9521 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9522 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9523 g_pMarkInfoDialog->Hide();
9524
9525 delete m_pRoutePointEditTarget;
9526 m_lastRoutePointEditTarget = NULL;
9527 m_pRoutePointEditTarget = NULL;
9528 undo->AfterUndoableAction(pMousePoint);
9529 undo->InvalidateUndo();
9530 }
9531 }
9532 }
9533
9534 else if (m_bMarkEditing) { // End of way point drag
9535 if (m_pRoutePointEditTarget)
9536 if (m_bRoutePoinDragging)
9537 pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9538 }
9539
9540 if (m_pRoutePointEditTarget)
9541 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9542
9543 if (!m_pRoutePointEditTarget) {
9544 delete m_pEditRouteArray;
9545 m_pEditRouteArray = NULL;
9546 m_bRouteEditing = false;
9547 }
9548 m_bRoutePoinDragging = false;
9549
9550 if (appending) { // Appending to the route of which the last point is
9551 // dragged onto another route
9552
9553 // copy tail from connect until length to end of current after dragging
9554
9555 int length = tail->GetnPoints();
9556 for (int i = connect + 1; i <= length; i++) {
9557 current->AddPointAndSegment(tail->GetPoint(i), false);
9558 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9559 m_routeState++;
9560 gFrame->RefreshAllCanvas();
9561 ret = true;
9562 }
9563 current->FinalizeForRendering();
9564 current->m_bIsBeingEdited = false;
9565 FinishRoute();
9566 g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9567 }
9568 if (inserting) {
9569 pSelect->DeleteAllSelectableRoutePoints(current);
9570 pSelect->DeleteAllSelectableRouteSegments(current);
9571 for (int i = 1; i < connect; i++) { // numbering in the tail route
9572 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9573 }
9574 pSelect->AddAllSelectableRouteSegments(current);
9575 pSelect->AddAllSelectableRoutePoints(current);
9576 current->FinalizeForRendering();
9577 current->m_bIsBeingEdited = false;
9578 g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9579 }
9580
9581 // Update the RouteProperties Dialog, if currently shown
9582 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9583 if (m_pEditRouteArray) {
9584 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9585 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9586 if (g_pRouteMan->IsRouteValid(pr)) {
9587 if (pRoutePropDialog->GetRoute() == pr) {
9588 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9589 }
9590 }
9591 }
9592 }
9593 }
9594
9595 } // g_btouch
9596
9597 else { // !g_btouch
9598 if (m_bRouteEditing) { // End of RoutePoint drag
9599 Route *tail = 0;
9600 Route *current = 0;
9601 bool appending = false;
9602 bool inserting = false;
9603 int connect = 0;
9604 int index_last;
9605 if (m_pRoutePointEditTarget) {
9606 m_pRoutePointEditTarget->m_bBlink = false;
9607 // Check to see if there is a nearby point which may replace the
9608 // dragged one
9609 RoutePoint *pMousePoint = NULL;
9610 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9611 double nearby_radius_meters =
9612 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9613 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9614 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9615 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9616 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9617 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9618 bool duplicate = false; // don't create duplicate point in routes
9619 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9620 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9621 ir++) {
9622 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9623 if (pr && pr->pRoutePointList) {
9624 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9625 wxNOT_FOUND) {
9626 duplicate = true;
9627 break;
9628 }
9629 }
9630 }
9631 }
9632
9633 // Special case:
9634 // Allow "re-use" of a route's waypoints iff it is a simple
9635 // isolated route. This allows, for instance, creation of a closed
9636 // polygon route
9637 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9638
9639 if (!duplicate) {
9640 int dlg_return;
9641 dlg_return =
9642 OCPNMessageBox(this,
9643 _("Replace this RoutePoint by the nearby "
9644 "Waypoint?"),
9645 _("OpenCPN RoutePoint change"),
9646 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9647 if (dlg_return == wxID_YES) {
9648 /*double confirmation if the dragged point has been manually
9649 * created which can be important and could be deleted
9650 * unintentionally*/
9651 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9652 pNearbyPoint);
9653 current =
9654 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9655
9656 if (tail && current && (tail != current)) {
9657 int dlg_return1;
9658 connect = tail->GetIndexOf(pNearbyPoint);
9659 int index_current_route =
9660 current->GetIndexOf(m_pRoutePointEditTarget);
9661 index_last = current->GetIndexOf(current->GetLastPoint());
9662 dlg_return1 = wxID_NO;
9663 if (index_last ==
9664 index_current_route) { // we are dragging the last
9665 // point of the route
9666 if (connect != tail->GetnPoints()) { // anything to do?
9667
9668 wxString dmsg(
9669 _("Last part of route to be appended to dragged "
9670 "route?"));
9671 if (connect == 1)
9672 dmsg =
9673 _("Full route to be appended to dragged route?");
9674
9675 dlg_return1 = OCPNMessageBox(
9676 this, dmsg, _("OpenCPN Route Create"),
9677 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9678 if (dlg_return1 == wxID_YES) {
9679 appending = true;
9680 }
9681 }
9682 } else if (index_current_route ==
9683 1) { // dragging the first point of the route
9684 if (connect != 1) { // anything to do?
9685
9686 wxString dmsg(
9687 _("First part of route to be inserted into dragged "
9688 "route?"));
9689 if (connect == tail->GetnPoints())
9690 dmsg = _(
9691 "Full route to be inserted into dragged route?");
9692
9693 dlg_return1 = OCPNMessageBox(
9694 this, dmsg, _("OpenCPN Route Create"),
9695 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9696 if (dlg_return1 == wxID_YES) {
9697 inserting = true;
9698 }
9699 }
9700 }
9701 }
9702
9703 if (m_pRoutePointEditTarget->IsShared()) {
9704 dlg_return = wxID_NO;
9705 dlg_return = OCPNMessageBox(
9706 this,
9707 _("Do you really want to delete and replace this "
9708 "WayPoint") +
9709 _T("\n") + _("which has been created manually?"),
9710 ("OpenCPN RoutePoint warning"),
9711 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9712 }
9713 }
9714 if (dlg_return == wxID_YES) {
9715 pMousePoint = pNearbyPoint;
9716 if (pMousePoint->m_bIsolatedMark) {
9717 pMousePoint->SetShared(true);
9718 }
9719 pMousePoint->m_bIsolatedMark =
9720 false; // definitely no longer isolated
9721 pMousePoint->m_bIsInRoute = true;
9722 }
9723 }
9724 }
9725 }
9726 if (!pMousePoint)
9727 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9728
9729 if (m_pEditRouteArray) {
9730 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9731 ir++) {
9732 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9733 if (g_pRouteMan->IsRouteValid(pr)) {
9734 if (pMousePoint) { // replace dragged point by nearby one
9735 int nRP =
9736 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9737
9738 pSelect->DeleteAllSelectableRoutePoints(pr);
9739 pSelect->DeleteAllSelectableRouteSegments(pr);
9740
9741 pr->pRoutePointList->Insert(nRP, pMousePoint);
9742 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9743
9744 pSelect->AddAllSelectableRouteSegments(pr);
9745 pSelect->AddAllSelectableRoutePoints(pr);
9746 }
9747 pr->FinalizeForRendering();
9748 pr->UpdateSegmentDistances();
9749 pr->m_bIsBeingEdited = false;
9750
9751 if (m_bRoutePoinDragging) pConfig->UpdateRoute(pr);
9752
9753 pr->SetHiLite(0);
9754 }
9755 }
9756 Refresh(false);
9757 }
9758
9759 if (appending) {
9760 // copy tail from connect until length to end of current after
9761 // dragging
9762
9763 int length = tail->GetnPoints();
9764 for (int i = connect + 1; i <= length; i++) {
9765 current->AddPointAndSegment(tail->GetPoint(i), false);
9766 if (current)
9767 current->m_lastMousePointIndex = current->GetnPoints();
9768 m_routeState++;
9769 gFrame->RefreshAllCanvas();
9770 ret = true;
9771 }
9772 current->FinalizeForRendering();
9773 current->m_bIsBeingEdited = false;
9774 FinishRoute();
9775 g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9776 }
9777 if (inserting) {
9778 pSelect->DeleteAllSelectableRoutePoints(current);
9779 pSelect->DeleteAllSelectableRouteSegments(current);
9780 for (int i = 1; i < connect; i++) { // numbering in the tail route
9781 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9782 }
9783 pSelect->AddAllSelectableRouteSegments(current);
9784 pSelect->AddAllSelectableRoutePoints(current);
9785 current->FinalizeForRendering();
9786 current->m_bIsBeingEdited = false;
9787 g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9788 }
9789
9790 // Update the RouteProperties Dialog, if currently shown
9791 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9792 if (m_pEditRouteArray) {
9793 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9794 ir++) {
9795 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9796 if (g_pRouteMan->IsRouteValid(pr)) {
9797 if (pRoutePropDialog->GetRoute() == pr) {
9798 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9799 }
9800 }
9801 }
9802 }
9803 }
9804
9805 if (pMousePoint) {
9806 pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9807 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9808 // Hide mark properties dialog if open on the replaced point
9809 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9810 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9811 g_pMarkInfoDialog->Hide();
9812
9813 delete m_pRoutePointEditTarget;
9814 m_lastRoutePointEditTarget = NULL;
9815 undo->AfterUndoableAction(pMousePoint);
9816 undo->InvalidateUndo();
9817 } else {
9818 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9819 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9820
9821 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9822 }
9823
9824 delete m_pEditRouteArray;
9825 m_pEditRouteArray = NULL;
9826 }
9827
9828 InvalidateGL();
9829 m_bRouteEditing = false;
9830 m_pRoutePointEditTarget = NULL;
9831
9832 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9833 ret = true;
9834 }
9835
9836 else if (m_bMarkEditing) { // end of Waypoint drag
9837 if (m_pRoutePointEditTarget) {
9838 if (m_bRoutePoinDragging)
9839 pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9840 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9841 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9842 if (!g_bopengl) {
9843 wxRect wp_rect;
9844 RoutePointGui(*m_pRoutePointEditTarget)
9845 .CalculateDCRect(m_dc_route, this, &wp_rect);
9846 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9847 RefreshRect(wp_rect, true);
9848 }
9849 }
9850 m_pRoutePointEditTarget = NULL;
9851 m_bMarkEditing = false;
9852 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9853 ret = true;
9854 }
9855
9856 else if (leftIsDown) { // left click for chart center
9857 leftIsDown = false;
9858 ret = false;
9859
9860 if (!g_btouch) {
9861 if (!m_bChartDragging && !m_bMeasure_Active) {
9862 } else {
9863 m_bChartDragging = false;
9864 }
9865 }
9866 }
9867 m_bRoutePoinDragging = false;
9868 } // !btouch
9869
9870 if (ret) return true;
9871 } // left up
9872
9873 if (event.RightDown()) {
9874 SetFocus(); // This is to let a plugin know which canvas is right-clicked
9875 last_drag.x = mx;
9876 last_drag.y = my;
9877
9878 if (g_btouch) {
9879 // if( m_pRoutePointEditTarget )
9880 // return false;
9881 }
9882
9883 ret = true;
9884 m_FinishRouteOnKillFocus = false;
9885 CallPopupMenu(mx, my);
9886 m_FinishRouteOnKillFocus = true;
9887 } // Right down
9888
9889 return ret;
9890}
9891
9892bool panleftIsDown;
9893
9894bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
9895 // Skip all mouse processing if shift is held.
9896 // This allows plugins to implement shift+drag behaviors.
9897 if (event.ShiftDown()) {
9898 return false;
9899 }
9900 int x, y;
9901 event.GetPosition(&x, &y);
9902
9903 x *= m_displayScale;
9904 y *= m_displayScale;
9905
9906 // Check for wheel rotation
9907 // ideally, should be just longer than the time between
9908 // processing accumulated mouse events from the event queue
9909 // as would happen during screen redraws.
9910 int wheel_dir = event.GetWheelRotation();
9911
9912 if (wheel_dir) {
9913 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
9914 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
9915
9916 double factor = g_mouse_zoom_sensitivity;
9917 if (wheel_dir < 0) factor = 1 / factor;
9918
9919 if (g_bsmoothpanzoom) {
9920 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
9921 if (wheel_dir == m_last_wheel_dir) {
9922 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
9923 // m_zoom_target /= factor;
9924 } else
9925 StopMovement();
9926 } else {
9927 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
9928 m_wheelstopwatch.Start(0);
9929 // m_zoom_target = VPoint.chart_scale / factor;
9930 }
9931 }
9932
9933 m_last_wheel_dir = wheel_dir;
9934
9935 ZoomCanvas(factor, true, false);
9936 }
9937
9938 if (event.LeftDown()) {
9939 // Skip the first left click if it will cause a canvas focus shift
9940 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
9941 // printf("focus shift\n");
9942 return false;
9943 }
9944
9945 last_drag.x = x, last_drag.y = y;
9946 panleftIsDown = true;
9947 }
9948
9949 if (event.LeftUp()) {
9950 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
9951 // seen here.
9952 panleftIsDown = false;
9953
9954 if (!g_btouch) {
9955 if (!m_bChartDragging && !m_bMeasure_Active) {
9956 switch (cursor_region) {
9957 case MID_RIGHT: {
9958 PanCanvas(100, 0);
9959 break;
9960 }
9961
9962 case MID_LEFT: {
9963 PanCanvas(-100, 0);
9964 break;
9965 }
9966
9967 case MID_TOP: {
9968 PanCanvas(0, 100);
9969 break;
9970 }
9971
9972 case MID_BOT: {
9973 PanCanvas(0, -100);
9974 break;
9975 }
9976
9977 case CENTER: {
9978 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
9979 break;
9980 }
9981 }
9982 } else {
9983 m_bChartDragging = false;
9984 }
9985 }
9986 }
9987 }
9988
9989 if (event.Dragging() && event.LeftIsDown()) {
9990 /*
9991 * fixed dragging.
9992 * On my Surface Pro 3 running Arch Linux there is no mouse down event
9993 * before the drag event. Hence, as there is no mouse down event, last_drag
9994 * is not reset before the drag. And that results in one single drag
9995 * session, meaning you cannot drag the map a few miles north, lift your
9996 * finger, and the go even further north. Instead, the map resets itself
9997 * always to the very first drag start (since there is not reset of
9998 * last_drag).
9999 *
10000 * Besides, should not left down and dragging be enough of a situation to
10001 * start a drag procedure?
10002 *
10003 * Anyways, guarded it to be active in touch situations only.
10004 */
10005
10006 if (g_btouch) {
10007 struct timespec now;
10008 clock_gettime(CLOCK_MONOTONIC, &now);
10009 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10010
10011 if (false == m_bChartDragging) {
10012 // Reset drag calculation members
10013 last_drag.x = x, last_drag.y = y;
10014 m_bChartDragging = true;
10015 m_chart_drag_total_time = 0;
10016 m_chart_drag_total_x = 0;
10017 m_chart_drag_total_y = 0;
10018 m_inertia_last_drag_x = x;
10019 m_inertia_last_drag_y = y;
10020 m_drag_vec_x.clear();
10021 m_drag_vec_y.clear();
10022 m_drag_vec_t.clear();
10023 m_last_drag_time = tnow;
10024 }
10025
10026 // Calculate and store drag dynamics.
10027 uint64_t delta_t = tnow - m_last_drag_time;
10028 double delta_tf = delta_t / 1e9;
10029
10030 m_chart_drag_total_time += delta_tf;
10031 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10032 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10033
10034 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10035 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10036 m_drag_vec_t.push_back(delta_tf);
10037
10038 m_inertia_last_drag_x = x;
10039 m_inertia_last_drag_y = y;
10040 m_last_drag_time = tnow;
10041
10042 if ((last_drag.x != x) || (last_drag.y != y)) {
10043 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10044 // dragging on route create.
10045 // github #2994
10046 m_bChartDragging = true;
10047 StartTimedMovement();
10048 m_pan_drag.x += last_drag.x - x;
10049 m_pan_drag.y += last_drag.y - y;
10050 last_drag.x = x, last_drag.y = y;
10051 }
10052 }
10053 } else {
10054 if ((last_drag.x != x) || (last_drag.y != y)) {
10055 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10056 // dragging on route create.
10057 // github #2994
10058 m_bChartDragging = true;
10059 StartTimedMovement();
10060 m_pan_drag.x += last_drag.x - x;
10061 m_pan_drag.y += last_drag.y - y;
10062 last_drag.x = x, last_drag.y = y;
10063 }
10064 }
10065 }
10066
10067 // Handle some special cases
10068 if (g_btouch) {
10069 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10070 // deactivate next LeftUp to ovoid creating an unexpected point
10071 m_DoubleClickTimer->Start();
10072 singleClickEventIsValid = false;
10073 }
10074 }
10075 }
10076
10077 return true;
10078}
10079
10080void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10081 if (MouseEventOverlayWindows(event)) return;
10082
10083 if (MouseEventSetup(event)) return; // handled, no further action required
10084
10085 if (!MouseEventProcessObjects(event)) MouseEventProcessCanvas(event);
10086}
10087
10088void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10089 // Switch to the appropriate cursor on mouse movement
10090
10091 wxCursor *ptarget_cursor = pCursorArrow;
10092 if (!pPlugIn_Cursor) {
10093 ptarget_cursor = pCursorArrow;
10094 if ((!m_routeState) &&
10095 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10096 if (cursor_region == MID_RIGHT) {
10097 ptarget_cursor = pCursorRight;
10098 } else if (cursor_region == MID_LEFT) {
10099 ptarget_cursor = pCursorLeft;
10100 } else if (cursor_region == MID_TOP) {
10101 ptarget_cursor = pCursorDown;
10102 } else if (cursor_region == MID_BOT) {
10103 ptarget_cursor = pCursorUp;
10104 } else {
10105 ptarget_cursor = pCursorArrow;
10106 }
10107 } else if (m_bMeasure_Active ||
10108 m_routeState) // If Measure tool use Pencil Cursor
10109 ptarget_cursor = pCursorPencil;
10110 } else {
10111 ptarget_cursor = pPlugIn_Cursor;
10112 }
10113
10114 SetCursor(*ptarget_cursor);
10115}
10116
10117void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10118 SetCursor(*pCursorArrow);
10119}
10120
10121void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10122 ChartPlugInWrapper *target_plugin_chart = NULL;
10123 s57chart *Chs57 = NULL;
10124 wxFileName file;
10125 wxArrayString files;
10126
10127 ChartBase *target_chart = GetChartAtCursor();
10128 if (target_chart) {
10129 file.Assign(target_chart->GetFullPath());
10130 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10131 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10132 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10133 else
10134 Chs57 = dynamic_cast<s57chart *>(target_chart);
10135 } else { // target_chart = null, might be mbtiles
10136 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10137 unsigned int im = stackIndexArray.size();
10138 int scale = 2147483647; // max 32b integer
10139 if (VPoint.b_quilt && im > 0) {
10140 for (unsigned int is = 0; is < im; is++) {
10141 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10142 CHART_TYPE_MBTILES) {
10143 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10144 double lat, lon;
10145 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10146 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10147 .GetBBox()
10148 .Contains(lat, lon)) {
10149 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10150 scale) {
10151 scale =
10152 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10153 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10154 }
10155 }
10156 }
10157 }
10158 }
10159 }
10160
10161 std::vector<Ais8_001_22 *> area_notices;
10162
10163 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10164 float vp_scale = GetVPScale();
10165
10166 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10167 auto target_data = target.second;
10168 if (!target_data->area_notices.empty()) {
10169 for (auto &ani : target_data->area_notices) {
10170 Ais8_001_22 &area_notice = ani.second;
10171
10172 BoundingBox bbox;
10173
10174 for (Ais8_001_22_SubAreaList::iterator sa =
10175 area_notice.sub_areas.begin();
10176 sa != area_notice.sub_areas.end(); ++sa) {
10177 switch (sa->shape) {
10178 case AIS8_001_22_SHAPE_CIRCLE: {
10179 wxPoint target_point;
10180 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10181 bbox.Expand(target_point);
10182 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10183 break;
10184 }
10185 case AIS8_001_22_SHAPE_RECT: {
10186 wxPoint target_point;
10187 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10188 bbox.Expand(target_point);
10189 if (sa->e_dim_m > sa->n_dim_m)
10190 bbox.EnLarge(sa->e_dim_m * vp_scale);
10191 else
10192 bbox.EnLarge(sa->n_dim_m * vp_scale);
10193 break;
10194 }
10195 case AIS8_001_22_SHAPE_POLYGON:
10196 case AIS8_001_22_SHAPE_POLYLINE: {
10197 for (int i = 0; i < 4; ++i) {
10198 double lat = sa->latitude;
10199 double lon = sa->longitude;
10200 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10201 &lat, &lon);
10202 wxPoint target_point;
10203 GetCanvasPointPix(lat, lon, &target_point);
10204 bbox.Expand(target_point);
10205 }
10206 break;
10207 }
10208 case AIS8_001_22_SHAPE_SECTOR: {
10209 double lat1 = sa->latitude;
10210 double lon1 = sa->longitude;
10211 double lat, lon;
10212 wxPoint target_point;
10213 GetCanvasPointPix(lat1, lon1, &target_point);
10214 bbox.Expand(target_point);
10215 for (int i = 0; i < 18; ++i) {
10216 ll_gc_ll(
10217 lat1, lon1,
10218 sa->left_bound_deg +
10219 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10220 sa->radius_m / 1852.0, &lat, &lon);
10221 GetCanvasPointPix(lat, lon, &target_point);
10222 bbox.Expand(target_point);
10223 }
10224 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10225 &lat, &lon);
10226 GetCanvasPointPix(lat, lon, &target_point);
10227 bbox.Expand(target_point);
10228 break;
10229 }
10230 }
10231 }
10232
10233 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10234 area_notices.push_back(&area_notice);
10235 }
10236 }
10237 }
10238 }
10239 }
10240
10241 if (target_chart || !area_notices.empty() || file.HasName()) {
10242 // Go get the array of all objects at the cursor lat/lon
10243 int sel_rad_pix = 5;
10244 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10245
10246 // Make sure we always get the lights from an object, even if we are
10247 // currently not displaying lights on the chart.
10248
10249 SetCursor(wxCURSOR_WAIT);
10250 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10251 if (!lightsVis) SetShowENCLights(true);
10252 ;
10253
10254 ListOfObjRazRules *rule_list = NULL;
10255 ListOfPI_S57Obj *pi_rule_list = NULL;
10256 if (Chs57)
10257 rule_list =
10258 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10259 else if (target_plugin_chart)
10260 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10261 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10262
10263 ListOfObjRazRules *overlay_rule_list = NULL;
10264 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10265 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10266
10267 if (CHs57_Overlay) {
10268 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10269 zlat, zlon, SelectRadius, &GetVP());
10270 }
10271
10272 if (!lightsVis) SetShowENCLights(false);
10273
10274 wxString objText;
10275 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10276 wxString face = dFont->GetFaceName();
10277
10278 if (NULL == g_pObjectQueryDialog) {
10279 g_pObjectQueryDialog =
10280 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10281 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10282 }
10283
10284 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10285 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10286
10287#ifdef __WXOSX__
10288 // Auto Adjustment for dark mode
10289 fg = g_pObjectQueryDialog->GetForegroundColour();
10290#endif
10291
10292 objText.Printf(
10293 _T("<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>"),
10294 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10295
10296#ifdef __WXOSX__
10297 int points = dFont->GetPointSize();
10298#else
10299 int points = dFont->GetPointSize() + 1;
10300#endif
10301
10302 int sizes[7];
10303 for (int i = -2; i < 5; i++) {
10304 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10305 }
10306 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10307
10308 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += _T("<i>");
10309
10310 if (overlay_rule_list && CHs57_Overlay) {
10311 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10312 objText << _T("<hr noshade>");
10313 }
10314
10315 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10316 an != area_notices.end(); ++an) {
10317 objText << _T( "<b>AIS Area Notice:</b> " );
10318 objText << ais8_001_22_notice_names[(*an)->notice_type];
10319 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10320 (*an)->sub_areas.begin();
10321 sa != (*an)->sub_areas.end(); ++sa)
10322 if (!sa->text.empty()) objText << sa->text;
10323 objText << _T( "<br>expires: " ) << (*an)->expiry_time.Format();
10324 objText << _T( "<hr noshade>" );
10325 }
10326
10327 if (Chs57)
10328 objText << Chs57->CreateObjDescriptions(rule_list);
10329 else if (target_plugin_chart)
10330 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10331 pi_rule_list);
10332
10333 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << _T("</i>");
10334
10335 // Add the additional info files
10336 wxString AddFiles, filenameOK;
10337 int filecount = 0;
10338 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10339 // plugin
10340
10341 AddFiles = wxString::Format(
10342 _T("<hr noshade><br><b>Additional info files attached to: </b> ")
10343 _T("<font ")
10344 _T("size=-2>%s</font><br><table border=0 cellspacing=0 ")
10345 _T("cellpadding=3>"),
10346 file.GetFullName());
10347 file.Normalize();
10348 file.Assign(file.GetPath(), wxT(""));
10349 wxDir dir(file.GetFullPath());
10350 wxString filename;
10351 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10352 while (cont) {
10353 file.Assign(dir.GetNameWithSep().append(filename));
10354 wxString FormatString =
10355 _T("<td valign=top><font size=-2><a ")
10356 _T("href=\"%s\">%s</a></font></td>");
10357 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10358 filenameOK = file.GetFullPath(); // remember last valid name
10359 // we are making a 3 columns table. New row only every third file
10360 if (3 * ((int)filecount / 3) == filecount)
10361 FormatString.Prepend(_T("<tr>")); // new row
10362 else
10363 FormatString.Prepend(
10364 _T("<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>")); // an empty
10365 // spacer column
10366
10367 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10368 file.GetFullName());
10369 filecount++;
10370 }
10371 cont = dir.GetNext(&filename);
10372 }
10373 objText << AddFiles << _T("</table>");
10374 }
10375 objText << _T("</font>");
10376 objText << _T("</body></html>");
10377
10378 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10379 g_pObjectQueryDialog->SetHTMLPage(objText);
10380 g_pObjectQueryDialog->Show();
10381 }
10382 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10383 // generate an event to avoid double code
10384 wxHtmlLinkInfo hli(filenameOK);
10385 wxHtmlLinkEvent hle(1, hli);
10386 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10387 }
10388
10389 if (rule_list) rule_list->Clear();
10390 delete rule_list;
10391
10392 if (overlay_rule_list) overlay_rule_list->Clear();
10393 delete overlay_rule_list;
10394
10395 if (pi_rule_list) pi_rule_list->Clear();
10396 delete pi_rule_list;
10397
10398 SetCursor(wxCURSOR_ARROW);
10399 }
10400}
10401
10402void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10403 bool bNew = false;
10404 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10405 // Dialog
10406 g_pMarkInfoDialog = new MarkInfoDlg(this);
10407 bNew = true;
10408 }
10409
10410 if (1 /*g_bresponsive*/) {
10411 wxSize canvas_size = GetSize();
10412
10413 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10414 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10415
10416 g_pMarkInfoDialog->Layout();
10417
10418 wxPoint canvas_pos = GetPosition();
10419 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10420
10421 bool newFit = false;
10422 if (canvas_size.x < fitted_size.x) {
10423 fitted_size.x = canvas_size.x - 40;
10424 if (canvas_size.y < fitted_size.y)
10425 fitted_size.y -= 40; // scrollbar added
10426 }
10427 if (canvas_size.y < fitted_size.y) {
10428 fitted_size.y = canvas_size.y - 40;
10429 if (canvas_size.x < fitted_size.x)
10430 fitted_size.x -= 40; // scrollbar added
10431 }
10432
10433 if (newFit) {
10434 g_pMarkInfoDialog->SetSize(fitted_size);
10435 g_pMarkInfoDialog->Centre();
10436 }
10437 }
10438
10439 markPoint->m_bRPIsBeingEdited = false;
10440
10441 wxString title_base = _("Mark Properties");
10442 if (markPoint->m_bIsInRoute) {
10443 title_base = _("Waypoint Properties");
10444 }
10445 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10446 g_pMarkInfoDialog->UpdateProperties();
10447 if (markPoint->m_bIsInLayer) {
10448 wxString caption(wxString::Format(_T("%s, %s: %s"), title_base, _("Layer"),
10449 GetLayerName(markPoint->m_LayerID)));
10450 g_pMarkInfoDialog->SetDialogTitle(caption);
10451 } else
10452 g_pMarkInfoDialog->SetDialogTitle(title_base);
10453
10454 g_pMarkInfoDialog->Show();
10455 g_pMarkInfoDialog->Raise();
10456 g_pMarkInfoDialog->InitialFocus();
10457 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10458}
10459
10460void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10461 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10462 pRoutePropDialog->SetRouteAndUpdate(selected);
10463 // pNew->UpdateProperties();
10464 pRoutePropDialog->Show();
10465 pRoutePropDialog->Raise();
10466 return;
10467 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10468 this); // There is one global instance of the RouteProp Dialog
10469
10470 if (g_bresponsive) {
10471 wxSize canvas_size = GetSize();
10472 wxPoint canvas_pos = GetPosition();
10473 wxSize fitted_size = pRoutePropDialog->GetSize();
10474 ;
10475
10476 if (canvas_size.x < fitted_size.x) {
10477 fitted_size.x = canvas_size.x;
10478 if (canvas_size.y < fitted_size.y)
10479 fitted_size.y -= 20; // scrollbar added
10480 }
10481 if (canvas_size.y < fitted_size.y) {
10482 fitted_size.y = canvas_size.y;
10483 if (canvas_size.x < fitted_size.x)
10484 fitted_size.x -= 20; // scrollbar added
10485 }
10486
10487 pRoutePropDialog->SetSize(fitted_size);
10488 pRoutePropDialog->Centre();
10489
10490 // int xp = (canvas_size.x - fitted_size.x)/2;
10491 // int yp = (canvas_size.y - fitted_size.y)/2;
10492
10493 wxPoint xxp = ClientToScreen(canvas_pos);
10494 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10495 }
10496
10497 pRoutePropDialog->SetRouteAndUpdate(selected);
10498
10499 pRoutePropDialog->Show();
10500
10501 Refresh(false);
10502}
10503
10504void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10505 pTrackPropDialog = TrackPropDlg::getInstance(
10506 this); // There is one global instance of the RouteProp Dialog
10507
10508 pTrackPropDialog->SetTrackAndUpdate(selected);
10509 pTrackPropDialog->UpdateProperties();
10510
10511 pTrackPropDialog->Show();
10512
10513 Refresh(false);
10514}
10515
10516void pupHandler_PasteWaypoint() {
10517 Kml kml;
10518
10519 int pasteBuffer = kml.ParsePasteBuffer();
10520 RoutePoint *pasted = kml.GetParsedRoutePoint();
10521 if (!pasted) return;
10522
10523 double nearby_radius_meters =
10524 g_Platform->GetSelectRadiusPix() /
10525 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10526
10527 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10528 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10529
10530 int answer = wxID_NO;
10531 if (nearPoint && !nearPoint->m_bIsInLayer) {
10532 wxString msg;
10533 msg << _(
10534 "There is an existing waypoint at the same location as the one you are "
10535 "pasting. Would you like to merge the pasted data with it?\n\n");
10536 msg << _("Answering 'No' will create a new waypoint at the same location.");
10537 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10538 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10539 }
10540
10541 if (answer == wxID_YES) {
10542 nearPoint->SetName(pasted->GetName());
10543 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10544 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10545 pRouteManagerDialog->UpdateWptListCtrl();
10546 }
10547
10548 if (answer == wxID_NO) {
10549 RoutePoint *newPoint = new RoutePoint(pasted);
10550 newPoint->m_bIsolatedMark = true;
10551 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10552 newPoint);
10553 pConfig->AddNewWayPoint(newPoint, -1);
10554 pWayPointMan->AddRoutePoint(newPoint);
10555 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10556 pRouteManagerDialog->UpdateWptListCtrl();
10557 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10558 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10559 }
10560
10561 gFrame->InvalidateAllGL();
10562 gFrame->RefreshAllCanvas(false);
10563}
10564
10565void pupHandler_PasteRoute() {
10566 Kml kml;
10567
10568 int pasteBuffer = kml.ParsePasteBuffer();
10569 Route *pasted = kml.GetParsedRoute();
10570 if (!pasted) return;
10571
10572 double nearby_radius_meters =
10573 g_Platform->GetSelectRadiusPix() /
10574 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10575
10576 RoutePoint *curPoint;
10577 RoutePoint *nearPoint;
10578 RoutePoint *prevPoint = NULL;
10579
10580 bool mergepoints = false;
10581 bool createNewRoute = true;
10582 int existingWaypointCounter = 0;
10583
10584 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10585 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10586 nearPoint = pWayPointMan->GetNearbyWaypoint(
10587 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10588 if (nearPoint) {
10589 mergepoints = true;
10590 existingWaypointCounter++;
10591 // Small hack here to avoid both extending RoutePoint and repeating all
10592 // the GetNearbyWaypoint calculations. Use existin data field in
10593 // RoutePoint as temporary storage.
10594 curPoint->m_bPtIsSelected = true;
10595 }
10596 }
10597
10598 int answer = wxID_NO;
10599 if (mergepoints) {
10600 wxString msg;
10601 msg << _(
10602 "There are existing waypoints at the same location as some of the ones "
10603 "you are pasting. Would you like to just merge the pasted data into "
10604 "them?\n\n");
10605 msg << _("Answering 'No' will create all new waypoints for this route.");
10606 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10607 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10608
10609 if (answer == wxID_CANCEL) {
10610 return;
10611 }
10612 }
10613
10614 // If all waypoints exist since before, and a route with the same name, we
10615 // don't create a new route.
10616 if (mergepoints && answer == wxID_YES &&
10617 existingWaypointCounter == pasted->GetnPoints()) {
10618 wxRouteListNode *route_node = pRouteList->GetFirst();
10619 while (route_node) {
10620 Route *proute = route_node->GetData();
10621
10622 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10623 createNewRoute = false;
10624 break;
10625 }
10626 route_node = route_node->GetNext();
10627 }
10628 }
10629
10630 Route *newRoute = 0;
10631 RoutePoint *newPoint = 0;
10632
10633 if (createNewRoute) {
10634 newRoute = new Route();
10635 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10636 }
10637
10638 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10639 curPoint = pasted->GetPoint(i);
10640 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10641 curPoint->m_bPtIsSelected = false;
10642 newPoint = pWayPointMan->GetNearbyWaypoint(
10643 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10644 newPoint->SetName(curPoint->GetName());
10645 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10646
10647 if (createNewRoute) newRoute->AddPoint(newPoint);
10648 } else {
10649 curPoint->m_bPtIsSelected = false;
10650
10651 newPoint = new RoutePoint(curPoint);
10652 newPoint->m_bIsolatedMark = false;
10653 newPoint->SetIconName(_T("circle"));
10654 newPoint->m_bIsVisible = true;
10655 newPoint->m_bShowName = false;
10656 newPoint->SetShared(false);
10657
10658 newRoute->AddPoint(newPoint);
10659 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10660 newPoint);
10661 pConfig->AddNewWayPoint(newPoint, -1);
10662 pWayPointMan->AddRoutePoint(newPoint);
10663 }
10664 if (i > 1 && createNewRoute)
10665 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10666 curPoint->m_lat, curPoint->m_lon,
10667 prevPoint, newPoint, newRoute);
10668 prevPoint = newPoint;
10669 }
10670
10671 if (createNewRoute) {
10672 pRouteList->Append(newRoute);
10673 pConfig->AddNewRoute(newRoute); // use auto next num
10674
10675 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10676 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10677 }
10678
10679 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10680 pRouteManagerDialog->UpdateRouteListCtrl();
10681 pRouteManagerDialog->UpdateWptListCtrl();
10682 }
10683 gFrame->InvalidateAllGL();
10684 gFrame->RefreshAllCanvas(false);
10685 }
10686 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10687 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10688}
10689
10690void pupHandler_PasteTrack() {
10691 Kml kml;
10692
10693 int pasteBuffer = kml.ParsePasteBuffer();
10694 Track *pasted = kml.GetParsedTrack();
10695 if (!pasted) return;
10696
10697 TrackPoint *curPoint;
10698
10699 Track *newTrack = new Track();
10700 TrackPoint *newPoint;
10701 TrackPoint *prevPoint = NULL;
10702
10703 newTrack->SetName(pasted->GetName());
10704
10705 for (int i = 0; i < pasted->GetnPoints(); i++) {
10706 curPoint = pasted->GetPoint(i);
10707
10708 newPoint = new TrackPoint(curPoint);
10709
10710 wxDateTime now = wxDateTime::Now();
10711 newPoint->SetCreateTime(curPoint->GetCreateTime());
10712
10713 newTrack->AddPoint(newPoint);
10714
10715 if (prevPoint)
10716 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10717 newPoint->m_lat, newPoint->m_lon,
10718 prevPoint, newPoint, newTrack);
10719
10720 prevPoint = newPoint;
10721 }
10722
10723 g_TrackList.push_back(newTrack);
10724 pConfig->AddNewTrack(newTrack);
10725
10726 gFrame->InvalidateAllGL();
10727 gFrame->RefreshAllCanvas(false);
10728}
10729
10730bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
10731 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
10732 m_pFoundRoutePoint, m_FoundAIS_MMSI,
10733 m_pIDXCandidate, m_nmea_log);
10734
10735 Connect(
10736 wxEVT_COMMAND_MENU_SELECTED,
10737 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10738
10739 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
10740
10741 Disconnect(
10742 wxEVT_COMMAND_MENU_SELECTED,
10743 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10744
10745 delete m_canvasMenu;
10746 m_canvasMenu = NULL;
10747
10748#ifdef __WXQT__
10749 // gFrame->SurfaceToolbar();
10750 // g_MainToolbar->Raise();
10751#endif
10752
10753 return true;
10754}
10755
10756void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
10757 // Pass menu events from the canvas to the menu handler
10758 // This is necessarily in ChartCanvas since that is the menu's parent.
10759 if (m_canvasMenu) {
10760 m_canvasMenu->PopupMenuHandler(event);
10761 }
10762 return;
10763}
10764
10765void ChartCanvas::StartRoute(void) {
10766 // Do not allow more than one canvas to create a route at one time.
10767 if (g_brouteCreating) return;
10768
10769 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
10770
10771 g_brouteCreating = true;
10772 m_routeState = 1;
10773 m_bDrawingRoute = false;
10774 SetCursor(*pCursorPencil);
10775 // SetCanvasToolbarItemState(ID_ROUTE, true);
10776 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
10777
10778 HideGlobalToolbar();
10779
10780#ifdef __ANDROID__
10781 androidSetRouteAnnunciator(true);
10782#endif
10783}
10784
10785void ChartCanvas::FinishRoute(void) {
10786 m_routeState = 0;
10787 m_prev_pMousePoint = NULL;
10788 m_bDrawingRoute = false;
10789
10790 // SetCanvasToolbarItemState(ID_ROUTE, false);
10791 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
10792#ifdef __ANDROID__
10793 androidSetRouteAnnunciator(false);
10794#endif
10795
10796 SetCursor(*pCursorArrow);
10797
10798 if (m_pMouseRoute) {
10799 if (m_bAppendingRoute)
10800 pConfig->UpdateRoute(m_pMouseRoute);
10801 else {
10802 if (m_pMouseRoute->GetnPoints() > 1) {
10803 pConfig->AddNewRoute(m_pMouseRoute);
10804 } else {
10805 g_pRouteMan->DeleteRoute(m_pMouseRoute,
10806 NavObjectChanges::getInstance());
10807 m_pMouseRoute = NULL;
10808 }
10809 }
10810 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
10811
10812 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
10813 (pRoutePropDialog->IsShown())) {
10814 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
10815 }
10816
10817 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
10818 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10819 pRouteManagerDialog->UpdateRouteListCtrl();
10820 }
10821 }
10822 m_bAppendingRoute = false;
10823 m_pMouseRoute = NULL;
10824
10825 m_pSelectedRoute = NULL;
10826
10827 undo->InvalidateUndo();
10828 gFrame->RefreshAllCanvas(true);
10829
10830 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
10831
10832 ShowGlobalToolbar();
10833
10834 g_brouteCreating = false;
10835}
10836
10837void ChartCanvas::HideGlobalToolbar() {
10838 if (m_canvasIndex == 0) {
10839 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
10840 }
10841}
10842
10843void ChartCanvas::ShowGlobalToolbar() {
10844 if (m_canvasIndex == 0) {
10845 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
10846 }
10847}
10848
10849void ChartCanvas::ShowAISTargetList(void) {
10850 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
10851 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
10852 }
10853
10854 g_pAISTargetList->UpdateAISTargetList();
10855}
10856
10857void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
10858 if (!m_bShowOutlines) return;
10859
10860 if (!ChartData) return;
10861
10862 int nEntry = ChartData->GetChartTableEntries();
10863
10864 for (int i = 0; i < nEntry; i++) {
10865 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
10866
10867 // Check to see if the candidate chart is in the currently active group
10868 bool b_group_draw = false;
10869 if (m_groupIndex > 0) {
10870 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
10871 int index = pt->GetGroupArray()[ig];
10872 if (m_groupIndex == index) {
10873 b_group_draw = true;
10874 break;
10875 }
10876 }
10877 } else
10878 b_group_draw = true;
10879
10880 if (b_group_draw) RenderChartOutline(dc, i, vp);
10881 }
10882
10883 // On CM93 Composite Charts, draw the outlines of the next smaller
10884 // scale cell
10885 cm93compchart *pcm93 = NULL;
10886 if (VPoint.b_quilt) {
10887 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
10888 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
10889 pcm93 = (cm93compchart *)pch;
10890 break;
10891 }
10892 } else if (m_singleChart &&
10893 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
10894 pcm93 = (cm93compchart *)m_singleChart;
10895
10896 if (pcm93) {
10897 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
10898 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
10899
10900 if (zoom_factor > 8.0) {
10901 wxPen mPen(GetGlobalColor(_T("UINFM")), 2, wxPENSTYLE_SHORT_DASH);
10902 dc.SetPen(mPen);
10903 } else {
10904 wxPen mPen(GetGlobalColor(_T("UINFM")), 1, wxPENSTYLE_SOLID);
10905 dc.SetPen(mPen);
10906 }
10907
10908 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
10909 }
10910}
10911
10912void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
10913#ifdef ocpnUSE_GL
10914 if (g_bopengl && m_glcc) {
10915 /* opengl version specially optimized */
10916 m_glcc->RenderChartOutline(dc, dbIndex, vp);
10917 return;
10918 }
10919#endif
10920
10921 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
10922 if (!ChartData->IsChartAvailable(dbIndex)) return;
10923 }
10924
10925 float plylat, plylon;
10926 float plylat1, plylon1;
10927
10928 int pixx, pixy, pixx1, pixy1;
10929
10930 LLBBox box;
10931 ChartData->GetDBBoundingBox(dbIndex, box);
10932
10933 // Don't draw an outline in the case where the chart covers the entire world
10934 // */
10935 if (box.GetLonRange() == 360) return;
10936
10937 double lon_bias = 0;
10938 // chart is outside of viewport lat/lon bounding box
10939 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
10940
10941 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10942
10943 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
10944 dc.SetPen(wxPen(GetGlobalColor(_T ( "YELO1" )), 1, wxPENSTYLE_SOLID));
10945
10946 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
10947 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFG" )), 1, wxPENSTYLE_SOLID));
10948
10949 else
10950 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFR" )), 1, wxPENSTYLE_SOLID));
10951
10952 // Are there any aux ply entries?
10953 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
10954 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
10955 {
10956 wxPoint r, r1;
10957
10958 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10959 plylon += lon_bias;
10960
10961 GetCanvasPointPix(plylat, plylon, &r);
10962 pixx = r.x;
10963 pixy = r.y;
10964
10965 for (int i = 0; i < nPly - 1; i++) {
10966 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
10967 plylon1 += lon_bias;
10968
10969 GetCanvasPointPix(plylat1, plylon1, &r1);
10970 pixx1 = r1.x;
10971 pixy1 = r1.y;
10972
10973 int pixxs1 = pixx1;
10974 int pixys1 = pixy1;
10975
10976 bool b_skip = false;
10977
10978 if (vp.chart_scale > 5e7) {
10979 // calculate projected distance between these two points in meters
10980 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
10981 pow((double)(pixy1 - pixy), 2)) /
10982 vp.view_scale_ppm;
10983
10984 if (dist > 0.0) {
10985 // calculate GC distance between these two points in meters
10986 double distgc =
10987 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
10988
10989 // If the distances are nonsense, it means that the scale is very
10990 // small and the segment wrapped the world So skip it....
10991 // TODO improve this to draw two segments
10992 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
10993 b_skip = true;
10994 } else
10995 b_skip = true;
10996 }
10997
10998 ClipResult res = cohen_sutherland_line_clip_i(
10999 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11000 if (res != Invisible && !b_skip)
11001 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11002
11003 plylat = plylat1;
11004 plylon = plylon1;
11005 pixx = pixxs1;
11006 pixy = pixys1;
11007 }
11008
11009 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11010 plylon1 += lon_bias;
11011
11012 GetCanvasPointPix(plylat1, plylon1, &r1);
11013 pixx1 = r1.x;
11014 pixy1 = r1.y;
11015
11016 ClipResult res = cohen_sutherland_line_clip_i(
11017 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11018 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11019 }
11020
11021 else // Use Aux PlyPoints
11022 {
11023 wxPoint r, r1;
11024
11025 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11026 for (int j = 0; j < nAuxPlyEntries; j++) {
11027 int nAuxPly =
11028 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11029 GetCanvasPointPix(plylat, plylon, &r);
11030 pixx = r.x;
11031 pixy = r.y;
11032
11033 for (int i = 0; i < nAuxPly - 1; i++) {
11034 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11035
11036 GetCanvasPointPix(plylat1, plylon1, &r1);
11037 pixx1 = r1.x;
11038 pixy1 = r1.y;
11039
11040 int pixxs1 = pixx1;
11041 int pixys1 = pixy1;
11042
11043 bool b_skip = false;
11044
11045 if (vp.chart_scale > 5e7) {
11046 // calculate projected distance between these two points in meters
11047 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11048 ((pixy1 - pixy) * (pixy1 - pixy))) /
11049 vp.view_scale_ppm;
11050 if (dist > 0.0) {
11051 // calculate GC distance between these two points in meters
11052 double distgc =
11053 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11054
11055 // If the distances are nonsense, it means that the scale is very
11056 // small and the segment wrapped the world So skip it....
11057 // TODO improve this to draw two segments
11058 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11059 b_skip = true;
11060 } else
11061 b_skip = true;
11062 }
11063
11064 ClipResult res = cohen_sutherland_line_clip_i(
11065 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11066 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11067
11068 plylat = plylat1;
11069 plylon = plylon1;
11070 pixx = pixxs1;
11071 pixy = pixys1;
11072 }
11073
11074 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11075 GetCanvasPointPix(plylat1, plylon1, &r1);
11076 pixx1 = r1.x;
11077 pixy1 = r1.y;
11078
11079 ClipResult res = cohen_sutherland_line_clip_i(
11080 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11081 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11082 }
11083 }
11084}
11085
11086static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
11087 const wxString &second) {
11088 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11089
11090 int pointsize = dFont->GetPointSize();
11091 pointsize /= OCPN_GetWinDIPScaleFactor();
11092
11093 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11094 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11095 false, dFont->GetFaceName());
11096
11097 dc.SetFont(*psRLI_font);
11098
11099 int w1, h1;
11100 int w2 = 0;
11101 int h2 = 0;
11102 int h, w;
11103
11104 int xp, yp;
11105 int hilite_offset = 3;
11106#ifdef __WXMAC__
11107 wxScreenDC sdc;
11108 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
11109 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
11110#else
11111 dc.GetTextExtent(first, &w1, &h1);
11112 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
11113#endif
11114
11115 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11116 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11117
11118 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
11119 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11120
11121 h = h1 + h2;
11122
11123 xp = ref_point.x - w;
11124 yp = ref_point.y;
11125 yp += hilite_offset;
11126
11127 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor(_T ( "YELO1" )), 172);
11128
11129 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" ))));
11130 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
11131
11132 dc.DrawText(first, xp, yp);
11133 if (second.Len()) dc.DrawText(second, xp, yp + h1);
11134}
11135
11136void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11137 if (!g_bAllowShipToActive) return;
11138
11139 Route *rt = g_pRouteMan->GetpActiveRoute();
11140 if (!rt) return;
11141
11142 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11143 wxPoint2DDouble pa, pb;
11144 GetDoubleCanvasPointPix(gLat, gLon, &pa);
11145 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11146
11147 // set pen
11148 int width =
11149 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11150 if (rt->m_width != wxPENSTYLE_INVALID)
11151 width = rt->m_width; // set route pen style if any
11152 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11153 g_shipToActiveStyle, 5)]; // get setting pen style
11154 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11155 wxColour color =
11156 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11157 : // set setting route pen color
11158 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11159 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11160
11161 dc.SetPen(*mypen);
11162 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11163
11164 if (!Use_Opengl)
11165 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11166 (int)pb.m_y, GetVP(), true);
11167
11168#ifdef ocpnUSE_GL
11169 else {
11170#ifdef USE_ANDROID_GLES2
11171 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11172#else
11173 if (style != wxPENSTYLE_SOLID) {
11174 if (glChartCanvas::dash_map.find(style) !=
11175 glChartCanvas::dash_map.end()) {
11176 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11177 dc.SetPen(*mypen);
11178 }
11179 }
11180 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11181#endif
11182
11183 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11184 (int)pb.m_x, (int)pb.m_y, GetVP());
11185 }
11186#endif
11187 }
11188}
11189
11190void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11191 Route *route = 0;
11192 if (m_routeState >= 2) route = m_pMouseRoute;
11193 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11194 route = m_pMeasureRoute;
11195
11196 if (!route) return;
11197
11198 // Validate route pointer
11199 if (!g_pRouteMan->IsRouteValid(route)) return;
11200
11201 double render_lat = m_cursor_lat;
11202 double render_lon = m_cursor_lon;
11203
11204 int np = route->GetnPoints();
11205 if (np) {
11206 if (g_btouch && (np > 1)) np--;
11207 RoutePoint rp = route->GetPoint(np);
11208 render_lat = rp.m_lat;
11209 render_lon = rp.m_lon;
11210 }
11211
11212 double rhumbBearing, rhumbDist;
11213 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11214 &rhumbBearing, &rhumbDist);
11215 double brg = rhumbBearing;
11216 double dist = rhumbDist;
11217
11218 // Skip GreatCircle rubberbanding on touch devices.
11219 if (!g_btouch) {
11220 double gcBearing, gcBearing2, gcDist;
11221 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11222 m_cursor_lat, &gcDist, &gcBearing,
11223 &gcBearing2);
11224 double gcDistm = gcDist / 1852.0;
11225
11226 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11227 rhumbBearing = 90.;
11228
11229 wxPoint destPoint, lastPoint;
11230
11231 route->m_NextLegGreatCircle = false;
11232 int milesDiff = rhumbDist - gcDistm;
11233 if (milesDiff > 1) {
11234 brg = gcBearing;
11235 dist = gcDistm;
11236 route->m_NextLegGreatCircle = true;
11237 }
11238
11239 // FIXME (MacOS, the first segment is rendered wrong)
11240 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11241 &lastPoint);
11242
11243 if (route->m_NextLegGreatCircle) {
11244 for (int i = 1; i <= milesDiff; i++) {
11245 double p = (double)i * (1.0 / (double)milesDiff);
11246 double pLat, pLon;
11247 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11248 &pLon, &pLat, &gcBearing2);
11249 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11250 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11251 false);
11252 lastPoint = destPoint;
11253 }
11254 } else {
11255 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11256 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11257 false);
11258 if (m_bMeasure_DistCircle) {
11259 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11260 powf((float)(r_rband.y - lastPoint.y), 2));
11261
11262 dc.SetPen(*g_pRouteMan->GetRoutePen());
11263 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11264 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11265 }
11266 }
11267 }
11268 }
11269
11270 wxString routeInfo;
11271 if (g_bShowTrue)
11272 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11273 0x00B0);
11274
11275 if (g_bShowMag) {
11276 double latAverage = (m_cursor_lat + render_lat) / 2;
11277 double lonAverage = (m_cursor_lon + render_lon) / 2;
11278 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11279
11280 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11281 (int)varBrg, 0x00B0);
11282 }
11283
11284 routeInfo << _T(" ") << FormatDistanceAdaptive(dist);
11285
11286 wxString s0;
11287 if (!route->m_bIsInLayer)
11288 s0.Append(_("Route") + _T(": "));
11289 else
11290 s0.Append(_("Layer Route: "));
11291
11292 double disp_length = route->m_route_length;
11293 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11294 s0 += FormatDistanceAdaptive(disp_length);
11295
11296 RouteLegInfo(dc, r_rband, routeInfo, s0);
11297
11298 m_brepaint_piano = true;
11299}
11300
11301void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11302 if (!m_bShowVisibleSectors) return;
11303
11304 if (g_bDeferredInitDone) {
11305 // need to re-evaluate sectors?
11306 double rhumbBearing, rhumbDist;
11307 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11308 &rhumbBearing, &rhumbDist);
11309
11310 if (rhumbDist > 0.05) // miles
11311 {
11312 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11313 m_sectorlegsVisible);
11314 m_sector_glat = gLat;
11315 m_sector_glon = gLon;
11316 }
11317 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11318 }
11319}
11320
11321void ChartCanvas::WarpPointerDeferred(int x, int y) {
11322 warp_x = x;
11323 warp_y = y;
11324 warp_flag = true;
11325}
11326
11327int s_msg;
11328
11329void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11330 if (!ps52plib) return;
11331
11332 if (VPoint.b_quilt) { // quilted
11333 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11334
11335 if (m_pQuilt->IsQuiltVector()) {
11336 if (ps52plib->GetStateHash() != m_s52StateHash) {
11337 UpdateS52State();
11338 m_s52StateHash = ps52plib->GetStateHash();
11339 }
11340 }
11341 } else {
11342 if (ps52plib->GetStateHash() != m_s52StateHash) {
11343 UpdateS52State();
11344 m_s52StateHash = ps52plib->GetStateHash();
11345 }
11346 }
11347
11348 // Plugin charts
11349 bool bSendPlibState = true;
11350 if (VPoint.b_quilt) { // quilted
11351 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11352 }
11353
11354 if (bSendPlibState) {
11355 wxJSONValue v;
11356 v[_T("OpenCPN Version Major")] = VERSION_MAJOR;
11357 v[_T("OpenCPN Version Minor")] = VERSION_MINOR;
11358 v[_T("OpenCPN Version Patch")] = VERSION_PATCH;
11359 v[_T("OpenCPN Version Date")] = VERSION_DATE;
11360 v[_T("OpenCPN Version Full")] = VERSION_FULL;
11361
11362 // S52PLIB state
11363 v[_T("OpenCPN S52PLIB ShowText")] = GetShowENCText();
11364 v[_T("OpenCPN S52PLIB ShowSoundings")] = GetShowENCDepth();
11365 v[_T("OpenCPN S52PLIB ShowLights")] = GetShowENCLights();
11366 v[_T("OpenCPN S52PLIB ShowAnchorConditions")] = m_encShowAnchor;
11367 v[_T("OpenCPN S52PLIB ShowQualityOfData")] = GetShowENCDataQual();
11368 v[_T("OpenCPN S52PLIB ShowATONLabel")] = GetShowENCBuoyLabels();
11369 v[_T("OpenCPN S52PLIB ShowLightDescription")] = GetShowENCLightDesc();
11370
11371 v[_T("OpenCPN S52PLIB DisplayCategory")] = GetENCDisplayCategory();
11372
11373 v[_T("OpenCPN S52PLIB SoundingsFactor")] = g_ENCSoundingScaleFactor;
11374 v[_T("OpenCPN S52PLIB TextFactor")] = g_ENCTextScaleFactor;
11375
11376 // Global S52 options
11377
11378 v[_T("OpenCPN S52PLIB MetaDisplay")] = ps52plib->m_bShowMeta;
11379 v[_T("OpenCPN S52PLIB DeclutterText")] = ps52plib->m_bDeClutterText;
11380 v[_T("OpenCPN S52PLIB ShowNationalText")] = ps52plib->m_bShowNationalTexts;
11381 v[_T("OpenCPN S52PLIB ShowImportantTextOnly")] =
11382 ps52plib->m_bShowS57ImportantTextOnly;
11383 v[_T("OpenCPN S52PLIB UseSCAMIN")] = ps52plib->m_bUseSCAMIN;
11384 v[_T("OpenCPN S52PLIB UseSUPER_SCAMIN")] = ps52plib->m_bUseSUPER_SCAMIN;
11385 v[_T("OpenCPN S52PLIB SymbolStyle")] = ps52plib->m_nSymbolStyle;
11386 v[_T("OpenCPN S52PLIB BoundaryStyle")] = ps52plib->m_nBoundaryStyle;
11387 v[_T("OpenCPN S52PLIB ColorShades")] =
11388 S52_getMarinerParam(S52_MAR_TWO_SHADES);
11389
11390 // Some global GUI parameters, for completeness
11391 v[_T("OpenCPN Zoom Mod Vector")] = g_chart_zoom_modifier_vector;
11392 v[_T("OpenCPN Zoom Mod Other")] = g_chart_zoom_modifier_raster;
11393 v[_T("OpenCPN Scale Factor Exp")] =
11394 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11395 v[_T("OpenCPN Display Width")] = (int)g_display_size_mm;
11396
11397 wxJSONWriter w;
11398 wxString out;
11399 w.Write(v, out);
11400
11401 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11402 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11403 g_lastS52PLIBPluginMessage = out;
11404 }
11405 }
11406}
11407int spaint;
11408int s_in_update;
11409void ChartCanvas::OnPaint(wxPaintEvent &event) {
11410 wxPaintDC dc(this);
11411
11412 // GetToolbar()->Show( m_bToolbarEnable );
11413
11414 // Paint updates may have been externally disabled (temporarily, to avoid
11415 // Yield() recursion performance loss) It is important that the wxPaintDC is
11416 // built, even if we elect to not process this paint message. Otherwise, the
11417 // paint message may not be removed from the message queue, esp on Windows.
11418 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11419
11420 if (!m_b_paint_enable) {
11421 return;
11422 }
11423
11424 // If necessary, reconfigure the S52 PLIB
11425 UpdateCanvasS52PLIBConfig();
11426
11427#ifdef ocpnUSE_GL
11428 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11429
11430 if (m_glcc && g_bopengl) {
11431 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11432 s_in_update++;
11433 m_glcc->Update();
11434 s_in_update--;
11435 }
11436
11437 return;
11438 }
11439#endif
11440
11441 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11442
11443 wxRegion ru = GetUpdateRegion();
11444
11445 int rx, ry, rwidth, rheight;
11446 ru.GetBox(rx, ry, rwidth, rheight);
11447 // printf("%d Onpaint update region box: %d %d %d %d\n", spaint++, rx, ry,
11448 // rwidth, rheight);
11449
11450#ifdef ocpnUSE_DIBSECTION
11451 ocpnMemDC temp_dc;
11452#else
11453 wxMemoryDC temp_dc;
11454#endif
11455
11456 long height = GetVP().pix_height;
11457
11458#ifdef __WXMAC__
11459 // On OS X we have to explicitly extend the region for the piano area
11460 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11461 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11462 height += m_Piano->GetHeight();
11463#endif // __WXMAC__
11464 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11465
11466 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11467 if (pthumbwin) {
11468 int thumbx, thumby, thumbsx, thumbsy;
11469 pthumbwin->GetPosition(&thumbx, &thumby);
11470 pthumbwin->GetSize(&thumbsx, &thumbsy);
11471 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11472
11473 if (pthumbwin->IsShown()) {
11474 rgn_chart.Subtract(rgn_thumbwin);
11475 ru.Subtract(rgn_thumbwin);
11476 }
11477 }
11478
11479 // subtract the chart bar if it isn't transparent, and determine if we need to
11480 // paint it
11481 wxRegion rgn_blit = ru;
11482 if (g_bShowChartBar) {
11483 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11484 GetClientSize().x, m_Piano->GetHeight());
11485
11486 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11487 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11488 if (style->chartStatusWindowTransparent)
11489 m_brepaint_piano = true;
11490 else
11491 ru.Subtract(chart_bar_rect);
11492 }
11493 }
11494
11495 if (m_Compass && m_Compass->IsShown()) {
11496 wxRect compassRect = m_Compass->GetRect();
11497 if (ru.Contains(compassRect) != wxOutRegion) {
11498 ru.Subtract(compassRect);
11499 }
11500 }
11501
11502 wxRect noteRect = m_notification_button->GetRect();
11503 if (ru.Contains(noteRect) != wxOutRegion) {
11504 ru.Subtract(noteRect);
11505 }
11506
11507 // Is this viewpoint the same as the previously painted one?
11508 bool b_newview = true;
11509
11510 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11511 (m_cache_vp.rotation == VPoint.rotation) &&
11512 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11513 m_cache_vp.IsValid()) {
11514 b_newview = false;
11515 }
11516
11517 // If the ViewPort is skewed or rotated, we may be able to use the cached
11518 // rotated bitmap.
11519 bool b_rcache_ok = false;
11520 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11521 b_rcache_ok = !b_newview;
11522
11523 // Make a special VP
11524 if (VPoint.b_MercatorProjectionOverride)
11525 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11526 ViewPort svp = VPoint;
11527
11528 svp.pix_width = svp.rv_rect.width;
11529 svp.pix_height = svp.rv_rect.height;
11530
11531 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11532 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11533 // VPoint.rv_rect.height);
11534
11535 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11536
11537 // If we are going to use the cached rotated image, there is no need to fetch
11538 // any chart data and this will do it...
11539 if (b_rcache_ok) chart_get_region.Clear();
11540
11541 // Blit pan acceleration
11542 if (VPoint.b_quilt) // quilted
11543 {
11544 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11545
11546 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11547
11548 bool busy = false;
11549 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11550 m_cache_vp.rotation != VPoint.rotation)) {
11551 AbstractPlatform::ShowBusySpinner();
11552 busy = true;
11553 }
11554
11555 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11556 (m_working_bm.GetHeight() != svp.pix_height))
11557 m_working_bm.Create(svp.pix_width, svp.pix_height,
11558 -1); // make sure the target is big enoug
11559
11560 if (fabs(VPoint.rotation) < 0.01) {
11561 bool b_save = true;
11562
11563 if (g_SencThreadManager) {
11564 if (g_SencThreadManager->GetJobCount()) {
11565 b_save = false;
11566 m_cache_vp.Invalidate();
11567 }
11568 }
11569
11570 // If the saved wxBitmap from last OnPaint is useable
11571 // calculate the blit parameters
11572
11573 // We can only do screen blit painting if subsequent ViewPorts differ by
11574 // whole pixels So, in small scale bFollow mode, force the full screen
11575 // render. This seems a hack....There may be better logic here.....
11576
11577 // if(m_bFollow)
11578 // b_save = false;
11579
11580 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11581 if (b_newview) {
11582 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11583 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11584
11585 int dy = c_new.y - c_old.y;
11586 int dx = c_new.x - c_old.x;
11587
11588 // printf("In OnPaint Trying Blit dx: %d
11589 // dy:%d\n\n", dx, dy);
11590
11591 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11592 if (dx || dy) {
11593 // Blit the reuseable portion of the cached wxBitmap to a working
11594 // bitmap
11595 temp_dc.SelectObject(m_working_bm);
11596
11597 wxMemoryDC cache_dc;
11598 cache_dc.SelectObject(m_cached_chart_bm);
11599
11600 if (dy > 0) {
11601 if (dx > 0) {
11602 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11603 VPoint.pix_height - dy, &cache_dc, dx, dy);
11604 } else {
11605 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11606 VPoint.pix_height - dy, &cache_dc, 0, dy);
11607 }
11608
11609 } else {
11610 if (dx > 0) {
11611 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11612 VPoint.pix_height + dy, &cache_dc, dx, 0);
11613 } else {
11614 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11615 VPoint.pix_height + dy, &cache_dc, 0, 0);
11616 }
11617 }
11618
11619 OCPNRegion update_region;
11620 if (dy) {
11621 if (dy > 0)
11622 update_region.Union(
11623 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11624 else
11625 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11626 }
11627
11628 if (dx) {
11629 if (dx > 0)
11630 update_region.Union(
11631 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11632 else
11633 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11634 }
11635
11636 // Render the new region
11637 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11638 update_region);
11639 cache_dc.SelectObject(wxNullBitmap);
11640 } else {
11641 // No sensible (dx, dy) change in the view, so use the cached
11642 // member bitmap
11643 temp_dc.SelectObject(m_cached_chart_bm);
11644 b_save = false;
11645 }
11646 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11647
11648 } else // not blitable
11649 {
11650 temp_dc.SelectObject(m_working_bm);
11651 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11652 chart_get_region);
11653 }
11654 } else {
11655 // No change in the view, so use the cached member bitmap2
11656 temp_dc.SelectObject(m_cached_chart_bm);
11657 b_save = false;
11658 }
11659 } else // cached bitmap is not yet valid
11660 {
11661 temp_dc.SelectObject(m_working_bm);
11662 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11663 chart_get_region);
11664 }
11665
11666 // Save the fully rendered quilt image as a wxBitmap member of this class
11667 if (b_save) {
11668 // if((m_cached_chart_bm.GetWidth() !=
11669 // svp.pix_width) ||
11670 // (m_cached_chart_bm.GetHeight() !=
11671 // svp.pix_height))
11672 // m_cached_chart_bm.Create(svp.pix_width,
11673 // svp.pix_height, -1); // target wxBitmap
11674 // is big enough
11675 wxMemoryDC scratch_dc_0;
11676 scratch_dc_0.SelectObject(m_cached_chart_bm);
11677 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11678
11679 scratch_dc_0.SelectObject(wxNullBitmap);
11680
11681 m_bm_cache_vp =
11682 VPoint; // save the ViewPort associated with the cached wxBitmap
11683 }
11684 }
11685
11686 else // quilted, rotated
11687 {
11688 temp_dc.SelectObject(m_working_bm);
11689 OCPNRegion chart_get_all_region(
11690 wxRect(0, 0, svp.pix_width, svp.pix_height));
11691 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11692 chart_get_all_region);
11693 }
11694
11695 AbstractPlatform::HideBusySpinner();
11696
11697 }
11698
11699 else // not quilted
11700 {
11701 if (!m_singleChart) {
11702 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
11703 dc.Clear();
11704 return;
11705 }
11706
11707 if (!chart_get_region.IsEmpty()) {
11708 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
11709 }
11710 }
11711
11712 if (temp_dc.IsOk()) {
11713 // Arrange to render the World Chart vector data behind the rendered
11714 // current chart so that uncovered canvas areas show at least the world
11715 // chart.
11716 OCPNRegion chartValidRegion;
11717 if (!VPoint.b_quilt) {
11718 // Make a region covering the current chart on the canvas
11719
11720 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
11721 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
11722 else {
11723 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
11724 // require that the viewport passed here have pix_width and pix_height
11725 // set to the actual display, not the virtual (rv_rect) sizes
11726 // (the vector calculations require the virtual sizes in svp)
11727
11728 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
11729 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
11730 }
11731 } else
11732 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
11733
11734 temp_dc.DestroyClippingRegion();
11735
11736 // Copy current chart region
11737 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
11738
11739 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
11740
11741 if (!backgroundRegion.IsEmpty()) {
11742 // Draw the Background Chart only in the areas NOT covered by the
11743 // current chart view
11744
11745 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
11746 clipping regions with more than 1 rectangle so... */
11747 wxColour water = pWorldBackgroundChart->water;
11748 if (water.IsOk()) {
11749 temp_dc.SetPen(*wxTRANSPARENT_PEN);
11750 temp_dc.SetBrush(wxBrush(water));
11751 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
11752 while (upd.HaveRects()) {
11753 wxRect rect = upd.GetRect();
11754 temp_dc.DrawRectangle(rect);
11755 upd.NextRect();
11756 }
11757 }
11758 // Associate with temp_dc
11759 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
11760 temp_dc.SetDeviceClippingRegion(*clip_region);
11761 delete clip_region;
11762
11763 ocpnDC bgdc(temp_dc);
11764 double r = VPoint.rotation;
11765 SetVPRotation(VPoint.skew);
11766
11767 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
11768 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
11769
11770 SetVPRotation(r);
11771 }
11772 } // temp_dc.IsOk();
11773
11774 wxMemoryDC *pChartDC = &temp_dc;
11775 wxMemoryDC rotd_dc;
11776
11777 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
11778 // Can we use the current rotated image cache?
11779 if (!b_rcache_ok) {
11780#ifdef __WXMSW__
11781 wxMemoryDC tbase_dc;
11782 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
11783 tbase_dc.SelectObject(bm_base);
11784 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11785 tbase_dc.SelectObject(wxNullBitmap);
11786#else
11787 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
11788#endif
11789
11790 wxImage base_image;
11791 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
11792
11793 // Use a local static image rotator to improve wxWidgets code profile
11794 // Especially, on GTK the wxRound and wxRealPoint functions are very
11795 // expensive.....
11796
11797 double angle = GetVP().skew - GetVP().rotation;
11798 wxImage ri;
11799 bool b_rot_ok = false;
11800 if (base_image.IsOk()) {
11801 ViewPort rot_vp = GetVP();
11802
11803 m_b_rot_hidef = false;
11804
11805 ri = Image_Rotate(
11806 base_image, angle,
11807 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
11808 m_b_rot_hidef, &m_roffset);
11809
11810 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11811 (rot_vp.rotation == VPoint.rotation) &&
11812 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
11813 rot_vp.IsValid() && (ri.IsOk())) {
11814 b_rot_ok = true;
11815 }
11816 }
11817
11818 if (b_rot_ok) {
11819 delete m_prot_bm;
11820 m_prot_bm = new wxBitmap(ri);
11821 }
11822
11823 m_roffset.x += VPoint.rv_rect.x;
11824 m_roffset.y += VPoint.rv_rect.y;
11825 }
11826
11827 if (m_prot_bm && m_prot_bm->IsOk()) {
11828 rotd_dc.SelectObject(*m_prot_bm);
11829 pChartDC = &rotd_dc;
11830 } else {
11831 pChartDC = &temp_dc;
11832 m_roffset = wxPoint(0, 0);
11833 }
11834 } else { // unrotated
11835 pChartDC = &temp_dc;
11836 m_roffset = wxPoint(0, 0);
11837 }
11838
11839 wxPoint offset = m_roffset;
11840
11841 // Save the PixelCache viewpoint for next time
11842 m_cache_vp = VPoint;
11843
11844 // Set up a scratch DC for overlay objects
11845 wxMemoryDC mscratch_dc;
11846 mscratch_dc.SelectObject(*pscratch_bm);
11847
11848 mscratch_dc.ResetBoundingBox();
11849 mscratch_dc.DestroyClippingRegion();
11850 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
11851
11852 // Blit the externally invalidated areas of the chart onto the scratch dc
11853 wxRegionIterator upd(rgn_blit); // get the update rect list
11854 while (upd) {
11855 wxRect rect = upd.GetRect();
11856
11857 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
11858 rect.x - offset.x, rect.y - offset.y);
11859 upd++;
11860 }
11861
11862 // If multi-canvas, indicate which canvas has keyboard focus
11863 // by drawing a simple blue bar at the top.
11864 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
11865 if (this == wxWindow::FindFocus()) {
11866 g_focusCanvas = this;
11867
11868 wxColour colour = GetGlobalColor(_T("BLUE4"));
11869 mscratch_dc.SetPen(wxPen(colour));
11870 mscratch_dc.SetBrush(wxBrush(colour));
11871
11872 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
11873 mscratch_dc.DrawRectangle(activeRect);
11874 }
11875 }
11876
11877 // Any MBtiles?
11878 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
11879 unsigned int im = stackIndexArray.size();
11880 if (VPoint.b_quilt && im > 0) {
11881 std::vector<int> tiles_to_show;
11882 for (unsigned int is = 0; is < im; is++) {
11883 const ChartTableEntry &cte =
11884 ChartData->GetChartTableEntry(stackIndexArray[is]);
11885 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
11886 continue;
11887 }
11888 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
11889 tiles_to_show.push_back(stackIndexArray[is]);
11890 }
11891 }
11892
11893 if (tiles_to_show.size())
11894 SetAlertString(_("MBTile requires OpenGL to be enabled"));
11895 }
11896
11897 // May get an unexpected OnPaint call while switching display modes
11898 // Guard for that.
11899 if (!g_bopengl) {
11900 // Draw the rest of the overlay objects directly on the scratch dc
11901 ocpnDC scratch_dc(mscratch_dc);
11902 DrawOverlayObjects(scratch_dc, ru);
11903
11904 if (m_bShowTide) {
11905 RebuildTideSelectList(GetVP().GetBBox());
11906 DrawAllTidesInBBox(scratch_dc, GetVP().GetBBox());
11907 }
11908
11909 if (m_bShowCurrent) {
11910 RebuildCurrentSelectList(GetVP().GetBBox());
11911 DrawAllCurrentsInBBox(scratch_dc, GetVP().GetBBox());
11912 }
11913
11914 if (m_brepaint_piano && g_bShowChartBar) {
11915 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), mscratch_dc);
11916 }
11917
11918 if (m_Compass) m_Compass->Paint(scratch_dc);
11919
11920 if (!g_CanvasHideNotificationIcon) {
11921 auto &noteman = NotificationManager::GetInstance();
11922 if (noteman.GetNotificationCount()) {
11923 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
11924 if (m_notification_button->UpdateStatus()) Refresh();
11925 m_notification_button->Show(true);
11926 m_notification_button->Paint(scratch_dc);
11927 } else {
11928 m_notification_button->Show(false);
11929 }
11930 }
11931 RenderAlertMessage(mscratch_dc, GetVP());
11932 }
11933
11934 // quiting?
11935 if (g_bquiting) {
11936#ifdef ocpnUSE_DIBSECTION
11937 ocpnMemDC q_dc;
11938#else
11939 wxMemoryDC q_dc;
11940#endif
11941 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
11942 q_dc.SelectObject(qbm);
11943
11944 // Get a copy of the screen
11945 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
11946
11947 // Draw a rectangle over the screen with a stipple brush
11948 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
11949 q_dc.SetBrush(qbr);
11950 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
11951
11952 // Blit back into source
11953 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
11954 wxCOPY);
11955
11956 q_dc.SelectObject(wxNullBitmap);
11957 }
11958#if 0
11959 // It is possible that this two-step method may be reuired for some platforms.
11960 // So, retain in the code base to aid recovery if necessary
11961
11962 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
11963 if( VPoint.b_quilt ) {
11964 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
11965 ChartBase *chart = m_pQuilt->GetRefChart();
11966 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
11967
11968 // Clear the text Global declutter list
11969 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
11970 if(ChPI)
11971 ChPI->ClearPLIBTextList();
11972 else{
11973 if(ps52plib)
11974 ps52plib->ClearTextList();
11975 }
11976
11977 wxMemoryDC t_dc;
11978 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
11979
11980 wxColor maskBackground = wxColour(1,0,0);
11981 t_dc.SelectObject( qbm );
11982 t_dc.SetBackground(wxBrush(maskBackground));
11983 t_dc.Clear();
11984
11985 // Copy the scratch DC into the new bitmap
11986 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
11987
11988 // Render the text to the new bitmap
11989 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
11990 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
11991
11992 // Copy the new bitmap back to the scratch dc
11993 wxRegionIterator upd_final( ru );
11994 while( upd_final ) {
11995 wxRect rect = upd_final.GetRect();
11996 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
11997 upd_final++;
11998 }
11999
12000 t_dc.SelectObject( wxNullBitmap );
12001 }
12002 }
12003 }
12004#endif
12005 // Direct rendering model...
12006 if (VPoint.b_quilt) {
12007 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12008 ChartBase *chart = m_pQuilt->GetRefChart();
12009 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12010 // Clear the text Global declutter list
12011 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12012 if (ChPI)
12013 ChPI->ClearPLIBTextList();
12014 else {
12015 if (ps52plib) ps52plib->ClearTextList();
12016 }
12017
12018 // Render the text directly to the scratch bitmap
12019 OCPNRegion chart_all_text_region(
12020 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12021
12022 if (g_bShowChartBar && m_Piano) {
12023 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12024 GetVP().pix_width, m_Piano->GetHeight());
12025
12026 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12027 if (!style->chartStatusWindowTransparent)
12028 chart_all_text_region.Subtract(chart_bar_rect);
12029 }
12030
12031 if (m_Compass && m_Compass->IsShown()) {
12032 wxRect compassRect = m_Compass->GetRect();
12033 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12034 chart_all_text_region.Subtract(compassRect);
12035 }
12036 }
12037
12038 mscratch_dc.DestroyClippingRegion();
12039
12040 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12041 chart_all_text_region);
12042 }
12043 }
12044 }
12045
12046 // And finally, blit the scratch dc onto the physical dc
12047 wxRegionIterator upd_final(rgn_blit);
12048 while (upd_final) {
12049 wxRect rect = upd_final.GetRect();
12050 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12051 rect.y);
12052 upd_final++;
12053 }
12054
12055 // Test code to validate the dc drawing rectangle....
12056 /*
12057 wxRegionIterator upd_ru ( rgn_blit ); // get the update rect list
12058 while ( upd_ru )
12059 {
12060 wxRect rect = upd_ru.GetRect();
12061
12062 dc.SetPen(wxPen(*wxRED));
12063 dc.SetBrush(wxBrush(*wxRED, wxTRANSPARENT));
12064 dc.DrawRectangle(rect);
12065 upd_ru ++ ;
12066 }
12067 */
12068
12069 // Deselect the chart bitmap from the temp_dc, so that it will not be
12070 // destroyed in the temp_dc dtor
12071 temp_dc.SelectObject(wxNullBitmap);
12072 // And for the scratch bitmap
12073 mscratch_dc.SelectObject(wxNullBitmap);
12074
12075 dc.DestroyClippingRegion();
12076
12077 PaintCleanup();
12078}
12079
12080void ChartCanvas::PaintCleanup() {
12081 // Handle the current graphic window, if present
12082
12083 if (pCwin) {
12084 pCwin->Show();
12085 if (m_bTCupdate) {
12086 pCwin->Refresh();
12087 pCwin->Update();
12088 }
12089 }
12090
12091 // And set flags for next time
12092 m_bTCupdate = false;
12093
12094 // Handle deferred WarpPointer
12095 if (warp_flag) {
12096 WarpPointer(warp_x, warp_y);
12097 warp_flag = false;
12098 }
12099
12100 // Start movement timers, this runs nearly immediately.
12101 // the reason we cannot simply call it directly is the
12102 // refresh events it emits may be blocked from this paint event
12103 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12104 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12105}
12106
12107#if 0
12108wxColour GetErrorGraphicColor(double val)
12109{
12110 /*
12111 double valm = wxMin(val_max, val);
12112
12113 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12114 unsigned char red = (unsigned char)(255 * (valm/val_max));
12115
12116 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12117
12118 hv.saturation = 1.0;
12119 hv.value = 1.0;
12120
12121 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12122 return wxColour(rv.red, rv.green, rv.blue);
12123 */
12124
12125 // HTML colors taken from NOAA WW3 Web representation
12126 wxColour c;
12127 if((val > 0) && (val < 1)) c.Set(_T("#002ad9"));
12128 else if((val >= 1) && (val < 2)) c.Set(_T("#006ed9"));
12129 else if((val >= 2) && (val < 3)) c.Set(_T("#00b2d9"));
12130 else if((val >= 3) && (val < 4)) c.Set(_T("#00d4d4"));
12131 else if((val >= 4) && (val < 5)) c.Set(_T("#00d9a6"));
12132 else if((val >= 5) && (val < 7)) c.Set(_T("#00d900"));
12133 else if((val >= 7) && (val < 9)) c.Set(_T("#95d900"));
12134 else if((val >= 9) && (val < 12)) c.Set(_T("#d9d900"));
12135 else if((val >= 12) && (val < 15)) c.Set(_T("#d9ae00"));
12136 else if((val >= 15) && (val < 18)) c.Set(_T("#d98300"));
12137 else if((val >= 18) && (val < 21)) c.Set(_T("#d95700"));
12138 else if((val >= 21) && (val < 24)) c.Set(_T("#d90000"));
12139 else if((val >= 24) && (val < 27)) c.Set(_T("#ae0000"));
12140 else if((val >= 27) && (val < 30)) c.Set(_T("#8c0000"));
12141 else if((val >= 30) && (val < 36)) c.Set(_T("#870000"));
12142 else if((val >= 36) && (val < 42)) c.Set(_T("#690000"));
12143 else if((val >= 42) && (val < 48)) c.Set(_T("#550000"));
12144 else if( val >= 48) c.Set(_T("#410000"));
12145
12146 return c;
12147}
12148
12149void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12150{
12151 wxImage gr_image(vp->pix_width, vp->pix_height);
12152 gr_image.InitAlpha();
12153
12154 double maxval = -10000;
12155 double minval = 10000;
12156
12157 double rlat, rlon;
12158 double glat, glon;
12159
12160 GetCanvasPixPoint(0, 0, rlat, rlon);
12161
12162 for(int i=1; i < vp->pix_height-1; i++)
12163 {
12164 for(int j=0; j < vp->pix_width; j++)
12165 {
12166 // Reference mercator value
12167// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12168
12169 // Georef value
12170 GetCanvasPixPoint(j, i, glat, glon);
12171
12172 maxval = wxMax(maxval, (glat - rlat));
12173 minval = wxMin(minval, (glat - rlat));
12174
12175 }
12176 rlat = glat;
12177 }
12178
12179 GetCanvasPixPoint(0, 0, rlat, rlon);
12180 for(int i=1; i < vp->pix_height-1; i++)
12181 {
12182 for(int j=0; j < vp->pix_width; j++)
12183 {
12184 // Reference mercator value
12185// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12186
12187 // Georef value
12188 GetCanvasPixPoint(j, i, glat, glon);
12189
12190 double f = ((glat - rlat)-minval)/(maxval - minval);
12191
12192 double dy = (f * 40);
12193
12194 wxColour c = GetErrorGraphicColor(dy);
12195 unsigned char r = c.Red();
12196 unsigned char g = c.Green();
12197 unsigned char b = c.Blue();
12198
12199 gr_image.SetRGB(j, i, r,g,b);
12200 if((glat - rlat )!= 0)
12201 gr_image.SetAlpha(j, i, 128);
12202 else
12203 gr_image.SetAlpha(j, i, 255);
12204
12205 }
12206 rlat = glat;
12207 }
12208
12209 // Create a Bitmap
12210 wxBitmap *pbm = new wxBitmap(gr_image);
12211 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12212 pbm->SetMask(gr_mask);
12213
12214 pmdc->DrawBitmap(*pbm, 0,0);
12215
12216 delete pbm;
12217
12218}
12219
12220#endif
12221
12222void ChartCanvas::CancelMouseRoute() {
12223 m_routeState = 0;
12224 m_pMouseRoute = NULL;
12225 m_bDrawingRoute = false;
12226}
12227
12228int ChartCanvas::GetNextContextMenuId() {
12229 return CanvasMenuHandler::GetNextContextMenuId();
12230}
12231
12232bool ChartCanvas::SetCursor(const wxCursor &c) {
12233#ifdef ocpnUSE_GL
12234 if (g_bopengl && m_glcc)
12235 return m_glcc->SetCursor(c);
12236 else
12237#endif
12238 return wxWindow::SetCursor(c);
12239}
12240
12241void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12242 if (g_bquiting) return;
12243 // Keep the mouse position members up to date
12244 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12245
12246 // Retrigger the route leg popup timer
12247 // This handles the case when the chart is moving in auto-follow mode,
12248 // but no user mouse input is made. The timer handler may Hide() the
12249 // popup if the chart moved enough n.b. We use slightly longer oneshot
12250 // value to allow this method's Refresh() to complete before potentially
12251 // getting another Refresh() in the popup timer handler.
12252 if (!m_RolloverPopupTimer.IsRunning() &&
12253 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12254 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12255 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12256 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12257
12258#ifdef ocpnUSE_GL
12259 if (m_glcc && g_bopengl) {
12260 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12261 // overlay objects.
12262 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12263
12264 m_glcc->Refresh(eraseBackground,
12265 NULL); // We always are going to render the entire screen
12266 // anyway, so make
12267 // sure that the window managers understand the invalid area
12268 // is actually the entire client area.
12269
12270 // We need to selectively Refresh some child windows, if they are visible.
12271 // Note that some children are refreshed elsewhere on timer ticks, so don't
12272 // need attention here.
12273
12274 // Thumbnail chart
12275 if (pthumbwin && pthumbwin->IsShown()) {
12276 pthumbwin->Raise();
12277 pthumbwin->Refresh(false);
12278 }
12279
12280 // ChartInfo window
12281 if (m_pCIWin && m_pCIWin->IsShown()) {
12282 m_pCIWin->Raise();
12283 m_pCIWin->Refresh(false);
12284 }
12285
12286 // if(g_MainToolbar)
12287 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12288
12289 } else
12290#endif
12291 wxWindow::Refresh(eraseBackground, rect);
12292}
12293
12294void ChartCanvas::Update() {
12295 if (m_glcc && g_bopengl) {
12296#ifdef ocpnUSE_GL
12297 m_glcc->Update();
12298#endif
12299 } else
12300 wxWindow::Update();
12301}
12302
12303void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12304 if (!pemboss) return;
12305 int x = pemboss->x, y = pemboss->y;
12306 const double factor = 200;
12307
12308 wxASSERT_MSG(dc.GetDC(), wxT("DrawEmboss has no dc (opengl?)"));
12309 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12310 wxASSERT_MSG(pmdc, wxT("dc to EmbossCanvas not a memory dc"));
12311
12312 // Grab a snipped image out of the chart
12313 wxMemoryDC snip_dc;
12314 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12315 snip_dc.SelectObject(snip_bmp);
12316
12317 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12318 snip_dc.SelectObject(wxNullBitmap);
12319
12320 wxImage snip_img = snip_bmp.ConvertToImage();
12321
12322 // Apply Emboss map to the snip image
12323 unsigned char *pdata = snip_img.GetData();
12324 if (pdata) {
12325 for (int y = 0; y < pemboss->height; y++) {
12326 int map_index = (y * pemboss->width);
12327 for (int x = 0; x < pemboss->width; x++) {
12328 double val = (pemboss->pmap[map_index] * factor) / 256.;
12329
12330 int nred = (int)((*pdata) + val);
12331 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12332 *pdata++ = (unsigned char)nred;
12333
12334 int ngreen = (int)((*pdata) + val);
12335 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12336 *pdata++ = (unsigned char)ngreen;
12337
12338 int nblue = (int)((*pdata) + val);
12339 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12340 *pdata++ = (unsigned char)nblue;
12341
12342 map_index++;
12343 }
12344 }
12345 }
12346
12347 // Convert embossed snip to a bitmap
12348 wxBitmap emb_bmp(snip_img);
12349
12350 // Map to another memoryDC
12351 wxMemoryDC result_dc;
12352 result_dc.SelectObject(emb_bmp);
12353
12354 // Blit to target
12355 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12356
12357 result_dc.SelectObject(wxNullBitmap);
12358}
12359
12360emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12361 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12362
12363 if (GetQuiltMode()) {
12364 // disable Overzoom indicator for MBTiles
12365 int refIndex = GetQuiltRefChartdbIndex();
12366 if (refIndex >= 0) {
12367 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12368 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12369 if (current_type == CHART_TYPE_MBTILES) {
12370 ChartBase *pChart = m_pQuilt->GetRefChart();
12371 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12372 if (ptc) {
12373 zoom_factor = ptc->GetZoomFactor();
12374 }
12375 }
12376 }
12377
12378 if (zoom_factor <= 3.9) return NULL;
12379 } else {
12380 if (m_singleChart) {
12381 if (zoom_factor <= 3.9) return NULL;
12382 } else
12383 return NULL;
12384 }
12385
12386 if (m_pEM_OverZoom) {
12387 m_pEM_OverZoom->x = 4;
12388 m_pEM_OverZoom->y = 0;
12389 if (g_MainToolbar && IsPrimaryCanvas()) {
12390 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12391 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12392 }
12393 }
12394 return m_pEM_OverZoom;
12395}
12396
12397void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12398 GridDraw(dc);
12399
12400 // bool pluginOverlayRender = true;
12401 //
12402 // if(g_canvasConfig > 0){ // Multi canvas
12403 // if(IsPrimaryCanvas())
12404 // pluginOverlayRender = false;
12405 // }
12406
12407 g_overlayCanvas = this;
12408
12409 if (g_pi_manager) {
12410 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12411 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12412 OVERLAY_LEGACY);
12413 }
12414
12415 AISDrawAreaNotices(dc, GetVP(), this);
12416
12417 wxDC *pdc = dc.GetDC();
12418 if (pdc) {
12419 pdc->DestroyClippingRegion();
12420 wxDCClipper(*pdc, ru);
12421 }
12422
12423 if (m_bShowNavobjects) {
12424 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12425 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12426 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12427 DrawAnchorWatchPoints(dc);
12428 } else {
12429 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12430 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12431 }
12432
12433 AISDraw(dc, GetVP(), this);
12434 ShipDraw(dc);
12435 AlertDraw(dc);
12436
12437 RenderVisibleSectorLights(dc);
12438
12439 RenderAllChartOutlines(dc, GetVP());
12440 RenderRouteLegs(dc);
12441 RenderShipToActive(dc, false);
12442 ScaleBarDraw(dc);
12443 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12444 if (g_pi_manager) {
12445 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12446 OVERLAY_OVER_SHIPS);
12447 }
12448
12449 DrawEmboss(dc, EmbossDepthScale());
12450 DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12451 if (g_pi_manager) {
12452 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12453 OVERLAY_OVER_EMBOSS);
12454 }
12455 if (!g_PrintingInProgress) {
12456 if (IsPrimaryCanvas()) {
12457 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12458 }
12459
12460 if (IsPrimaryCanvas()) {
12461 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12462 }
12463
12464 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12465
12466 if (m_pTrackRolloverWin) {
12467 m_pTrackRolloverWin->Draw(dc);
12468 m_brepaint_piano = true;
12469 }
12470
12471 if (m_pRouteRolloverWin) {
12472 m_pRouteRolloverWin->Draw(dc);
12473 m_brepaint_piano = true;
12474 }
12475
12476 if (m_pAISRolloverWin) {
12477 m_pAISRolloverWin->Draw(dc);
12478 m_brepaint_piano = true;
12479 }
12480 }
12481 if (g_pi_manager) {
12482 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12483 OVERLAY_OVER_UI);
12484 }
12485}
12486
12487emboss_data *ChartCanvas::EmbossDepthScale() {
12488 if (!m_bShowDepthUnits) return NULL;
12489
12490 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12491
12492 if (GetQuiltMode()) {
12493 wxString s = m_pQuilt->GetQuiltDepthUnit();
12494 s.MakeUpper();
12495 if (s == _T("FEET"))
12496 depth_unit_type = DEPTH_UNIT_FEET;
12497 else if (s.StartsWith(_T("FATHOMS")))
12498 depth_unit_type = DEPTH_UNIT_FATHOMS;
12499 else if (s.StartsWith(_T("METERS")))
12500 depth_unit_type = DEPTH_UNIT_METERS;
12501 else if (s.StartsWith(_T("METRES")))
12502 depth_unit_type = DEPTH_UNIT_METERS;
12503 else if (s.StartsWith(_T("METRIC")))
12504 depth_unit_type = DEPTH_UNIT_METERS;
12505 else if (s.StartsWith(_T("METER")))
12506 depth_unit_type = DEPTH_UNIT_METERS;
12507
12508 } else {
12509 if (m_singleChart) {
12510 depth_unit_type = m_singleChart->GetDepthUnitType();
12511 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12512 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12513 }
12514 }
12515
12516 emboss_data *ped = NULL;
12517 switch (depth_unit_type) {
12518 case DEPTH_UNIT_FEET:
12519 ped = m_pEM_Feet;
12520 break;
12521 case DEPTH_UNIT_METERS:
12522 ped = m_pEM_Meters;
12523 break;
12524 case DEPTH_UNIT_FATHOMS:
12525 ped = m_pEM_Fathoms;
12526 break;
12527 default:
12528 return NULL;
12529 }
12530
12531 ped->x = (GetVP().pix_width - ped->width);
12532
12533 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12534 wxRect r = m_Compass->GetRect();
12535 ped->y = r.y + r.height;
12536 } else {
12537 ped->y = 40;
12538 }
12539 return ped;
12540}
12541
12542void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12543 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12544 wxFont font;
12545 if (style->embossFont == wxEmptyString) {
12546 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12547 font = *dFont;
12548 font.SetPointSize(60);
12549 font.SetWeight(wxFONTWEIGHT_BOLD);
12550 } else
12551 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12552 wxFONTWEIGHT_BOLD, false, style->embossFont);
12553
12554 int emboss_width = 500;
12555 int emboss_height = 200;
12556
12557 // Free any existing emboss maps
12558 delete m_pEM_Feet;
12559 delete m_pEM_Meters;
12560 delete m_pEM_Fathoms;
12561
12562 // Create the 3 DepthUnit emboss map structures
12563 m_pEM_Feet =
12564 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12565 m_pEM_Meters =
12566 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12567 m_pEM_Fathoms =
12568 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12569}
12570
12571#define OVERZOOM_TEXT _("OverZoom")
12572
12573void ChartCanvas::SetOverzoomFont() {
12574 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12575 int w, h;
12576
12577 wxFont font;
12578 if (style->embossFont == wxEmptyString) {
12579 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12580 font = *dFont;
12581 font.SetPointSize(40);
12582 font.SetWeight(wxFONTWEIGHT_BOLD);
12583 } else
12584 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12585 wxFONTWEIGHT_BOLD, false, style->embossFont);
12586
12587 wxClientDC dc(this);
12588 dc.SetFont(font);
12589 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12590
12591 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12592 font.SetPointSize(font.GetPointSize() - 1);
12593 dc.SetFont(font);
12594 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12595 }
12596 m_overzoomFont = font;
12597 m_overzoomTextWidth = w;
12598 m_overzoomTextHeight = h;
12599}
12600
12601void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12602 delete m_pEM_OverZoom;
12603
12604 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12605 m_pEM_OverZoom =
12606 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12607 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12608}
12609
12610emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12611 int height, const wxString &str,
12612 ColorScheme cs) {
12613 int *pmap;
12614
12615 // Create a temporary bitmap
12616 wxBitmap bmp(width, height, -1);
12617
12618 // Create a memory DC
12619 wxMemoryDC temp_dc;
12620 temp_dc.SelectObject(bmp);
12621
12622 // Paint on it
12623 temp_dc.SetBackground(*wxWHITE_BRUSH);
12624 temp_dc.SetTextBackground(*wxWHITE);
12625 temp_dc.SetTextForeground(*wxBLACK);
12626
12627 temp_dc.Clear();
12628
12629 temp_dc.SetFont(font);
12630
12631 int str_w, str_h;
12632 temp_dc.GetTextExtent(str, &str_w, &str_h);
12633 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12634 temp_dc.DrawText(str, 1, 1);
12635
12636 // Deselect the bitmap
12637 temp_dc.SelectObject(wxNullBitmap);
12638
12639 // Convert bitmap the wxImage for manipulation
12640 wxImage img = bmp.ConvertToImage();
12641
12642 int image_width = str_w * 105 / 100;
12643 int image_height = str_h * 105 / 100;
12644 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12645 wxMin(image_height, img.GetHeight()));
12646 wxImage imgs = img.GetSubImage(r);
12647
12648 double val_factor;
12649 switch (cs) {
12650 case GLOBAL_COLOR_SCHEME_DAY:
12651 default:
12652 val_factor = 1;
12653 break;
12654 case GLOBAL_COLOR_SCHEME_DUSK:
12655 val_factor = .5;
12656 break;
12657 case GLOBAL_COLOR_SCHEME_NIGHT:
12658 val_factor = .25;
12659 break;
12660 }
12661
12662 int val;
12663 int index;
12664 const int w = imgs.GetWidth();
12665 const int h = imgs.GetHeight();
12666 pmap = (int *)calloc(w * h * sizeof(int), 1);
12667 // Create emboss map by differentiating the emboss image
12668 // and storing integer results in pmap
12669 // n.b. since the image is B/W, it is sufficient to check
12670 // one channel (i.e. red) only
12671 for (int y = 1; y < h - 1; y++) {
12672 for (int x = 1; x < w - 1; x++) {
12673 val =
12674 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
12675 val = (int)(val * val_factor);
12676 index = (y * w) + x;
12677 pmap[index] = val;
12678 }
12679 }
12680
12681 emboss_data *pret = new emboss_data;
12682 pret->pmap = pmap;
12683 pret->width = w;
12684 pret->height = h;
12685
12686 return pret;
12687}
12688
12689void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12690 Track *active_track = NULL;
12691 for (Track *pTrackDraw : g_TrackList) {
12692 if (g_pActiveTrack == pTrackDraw) {
12693 active_track = pTrackDraw;
12694 continue;
12695 }
12696
12697 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
12698 }
12699
12700 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12701}
12702
12703void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12704 Track *active_track = NULL;
12705 for (Track *pTrackDraw : g_TrackList) {
12706 if (g_pActiveTrack == pTrackDraw) {
12707 active_track = pTrackDraw;
12708 break;
12709 }
12710 }
12711 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12712}
12713
12714void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12715 Route *active_route = NULL;
12716
12717 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12718 node = node->GetNext()) {
12719 Route *pRouteDraw = node->GetData();
12720 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12721 active_route = pRouteDraw;
12722 continue;
12723 }
12724
12725 // if(m_canvasIndex == 1)
12726 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
12727 }
12728
12729 // Draw any active or selected route (or track) last, so that is is always on
12730 // top
12731 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12732}
12733
12734void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12735 Route *active_route = NULL;
12736
12737 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12738 node = node->GetNext()) {
12739 Route *pRouteDraw = node->GetData();
12740 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12741 active_route = pRouteDraw;
12742 break;
12743 }
12744 }
12745 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12746}
12747
12748void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12749 if (!pWayPointMan) return;
12750
12751 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12752
12753 while (node) {
12754 RoutePoint *pWP = node->GetData();
12755 if (pWP) {
12756 if (pWP->m_bIsInRoute) {
12757 node = node->GetNext();
12758 continue;
12759 }
12760
12761 /* technically incorrect... waypoint has bounding box */
12762 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
12763 RoutePointGui(*pWP).Draw(dc, this, NULL);
12764 else {
12765 // Are Range Rings enabled?
12766 if (pWP->GetShowWaypointRangeRings() &&
12767 (pWP->GetWaypointRangeRingsNumber() > 0)) {
12768 double factor = 1.00;
12769 if (pWP->GetWaypointRangeRingsStepUnits() ==
12770 1) // convert kilometers to NMi
12771 factor = 1 / 1.852;
12772
12773 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
12774 pWP->GetWaypointRangeRingsStep() / 60.;
12775 radius *= 2; // Fudge factor
12776
12777 LLBBox radar_box;
12778 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
12779 pWP->m_lat + radius, pWP->m_lon + radius);
12780 if (!BltBBox.IntersectOut(radar_box)) {
12781 RoutePointGui(*pWP).Draw(dc, this, NULL);
12782 }
12783 }
12784 }
12785 }
12786
12787 node = node->GetNext();
12788 }
12789}
12790
12791void ChartCanvas::DrawBlinkObjects(void) {
12792 // All RoutePoints
12793 wxRect update_rect;
12794
12795 if (!pWayPointMan) return;
12796
12797 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12798
12799 while (node) {
12800 RoutePoint *pWP = node->GetData();
12801 if (pWP) {
12802 if (pWP->m_bBlink) {
12803 update_rect.Union(pWP->CurrentRect_in_DC);
12804 }
12805 }
12806
12807 node = node->GetNext();
12808 }
12809 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
12810}
12811
12812void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
12813 // draw anchor watch rings, if activated
12814
12815 if (pAnchorWatchPoint1 || pAnchorWatchPoint2) {
12816 wxPoint r1, r2;
12817 wxPoint lAnchorPoint1, lAnchorPoint2;
12818 double lpp1 = 0.0;
12819 double lpp2 = 0.0;
12820 if (pAnchorWatchPoint1) {
12821 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
12822 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
12823 &lAnchorPoint1);
12824 }
12825 if (pAnchorWatchPoint2) {
12826 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
12827 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
12828 &lAnchorPoint2);
12829 }
12830
12831 wxPen ppPeng(GetGlobalColor(_T ( "UGREN" )), 2);
12832 wxPen ppPenr(GetGlobalColor(_T ( "URED" )), 2);
12833
12834 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
12835 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
12836 dc.SetBrush(*ppBrush);
12837
12838 if (lpp1 > 0) {
12839 dc.SetPen(ppPeng);
12840 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12841 }
12842
12843 if (lpp2 > 0) {
12844 dc.SetPen(ppPeng);
12845 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12846 }
12847
12848 if (lpp1 < 0) {
12849 dc.SetPen(ppPenr);
12850 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12851 }
12852
12853 if (lpp2 < 0) {
12854 dc.SetPen(ppPenr);
12855 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12856 }
12857 }
12858}
12859
12860double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
12861 double lpp = 0.;
12862 wxPoint r1;
12863 wxPoint lAnchorPoint;
12864 double d1 = 0.0;
12865 double dabs;
12866 double tlat1, tlon1;
12867
12868 if (pAnchorWatchPoint) {
12869 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
12870 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
12871 dabs = fabs(d1 / 1852.);
12872 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
12873 &tlat1, &tlon1);
12874 GetCanvasPointPix(tlat1, tlon1, &r1);
12875 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
12876 &lAnchorPoint);
12877 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
12878 pow((double)(lAnchorPoint.y - r1.y), 2));
12879
12880 // This is an entry watch
12881 if (d1 < 0) lpp = -lpp;
12882 }
12883 return lpp;
12884}
12885
12886//------------------------------------------------------------------------------------------
12887// Tides Support
12888//------------------------------------------------------------------------------------------
12889void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
12890 if (!ptcmgr) return;
12891
12892 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
12893
12894 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12895 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12896 double lon = pIDX->IDX_lon;
12897 double lat = pIDX->IDX_lat;
12898
12899 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12900 if ((type == 't') || (type == 'T')) {
12901 if (BBox.Contains(lat, lon)) {
12902 // Manage the point selection list
12903 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
12904 }
12905 }
12906 }
12907}
12908
12909extern wxDateTime gTimeSource;
12910
12911void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
12912 if (!ptcmgr) return;
12913
12914 wxDateTime this_now = gTimeSource;
12915 bool cur_time = !gTimeSource.IsValid();
12916 if (cur_time) this_now = wxDateTime::Now();
12917 time_t t_this_now = this_now.GetTicks();
12918
12919 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
12920 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
12921 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
12922 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )), 1,
12923 wxPENSTYLE_SOLID);
12924 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
12925 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )), 1,
12926 wxPENSTYLE_SOLID);
12927
12928 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
12929 GetGlobalColor(_T ( "GREEN1" )), wxBRUSHSTYLE_SOLID);
12930 // wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush (
12931 // GetGlobalColor ( _T ( "UINFD" ) ), wxSOLID );
12932 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
12933 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )),
12934 wxBRUSHSTYLE_SOLID);
12935 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
12936 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )),
12937 wxBRUSHSTYLE_SOLID);
12938
12939 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
12940 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
12941 int font_size = wxMax(10, dFont->GetPointSize());
12942 font_size /= g_Platform->GetDisplayDIPMult(this);
12943 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
12944 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
12945 false, dFont->GetFaceName());
12946
12947 dc.SetPen(*pblack_pen);
12948 dc.SetBrush(*pgreen_brush);
12949
12950 wxBitmap bm;
12951 switch (m_cs) {
12952 case GLOBAL_COLOR_SCHEME_DAY:
12953 bm = m_bmTideDay;
12954 break;
12955 case GLOBAL_COLOR_SCHEME_DUSK:
12956 bm = m_bmTideDusk;
12957 break;
12958 case GLOBAL_COLOR_SCHEME_NIGHT:
12959 bm = m_bmTideNight;
12960 break;
12961 default:
12962 bm = m_bmTideDay;
12963 break;
12964 }
12965
12966 int bmw = bm.GetWidth();
12967 int bmh = bm.GetHeight();
12968
12969 float scale_factor = 1.0;
12970
12971 // Set the onscreen size of the symbol
12972 // Compensate for various display resolutions
12973 float icon_pixelRefDim = 45;
12974
12975#if 0
12976 float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *25 / 1000; // Intended physical rendered size onscreen
12977 nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 8);
12978 nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 15);
12979 float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
12980#endif
12981
12982#ifndef __ANDROID__
12983 // another method is simply to declare that the icon shall be x times the size
12984 // of a raster symbol (e.g.BOYLAT)
12985 // This is a bit of a hack that will suffice until until we get fully
12986 // scalable ENC symbol sets
12987 // float nominal_icon_size_pixels = 48; // 3 x 16
12988 // float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
12989
12990 // or, x times size of text font
12991 wxScreenDC sdc;
12992 int height;
12993 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
12994 height *= g_Platform->GetDisplayDIPMult(this);
12995 float nominal_icon_size_pixels = 48; // 3 x 16
12996 float pix_factor = (2 * height) / nominal_icon_size_pixels;
12997
12998#else
12999 // Yet another method goes like this:
13000 // Set the onscreen size of the symbol
13001 // Compensate for various display resolutions
13002 // Develop empirically, making a symbol about 16 mm tall
13003 double symHeight =
13004 icon_pixelRefDim /
13005 GetPixPerMM(); // from draw instructions, symbol is xx pix high
13006 double targetHeight0 = 16.0;
13007
13008 // But we want to scale the size down for smaller displays
13009 double displaySize = m_display_size_mm;
13010 displaySize = wxMax(displaySize, 100);
13011
13012 float targetHeight = wxMin(targetHeight0, displaySize / 15);
13013
13014 double pix_factor = targetHeight / symHeight;
13015#endif
13016
13017 scale_factor *= pix_factor;
13018
13019 float user_scale_factor = g_ChartScaleFactorExp;
13020 if (g_ChartScaleFactorExp > 1.0)
13021 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13022 1.2; // soften the scale factor a bit
13023
13024 scale_factor *= user_scale_factor;
13025 scale_factor *= GetContentScaleFactor();
13026
13027 {
13028 double marge = 0.05;
13029 std::vector<LLBBox> drawn_boxes;
13030 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13031 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13032
13033 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13034 if ((type == 't') || (type == 'T')) // only Tides
13035 {
13036 double lon = pIDX->IDX_lon;
13037 double lat = pIDX->IDX_lat;
13038
13039 if (BBox.ContainsMarge(lat, lon, marge)) {
13040 // Avoid drawing detailed graphic for duplicate tide stations
13041 if (GetVP().chart_scale < 500000) {
13042 bool bdrawn = false;
13043 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13044 if (drawn_boxes[i].Contains(lat, lon)) {
13045 bdrawn = true;
13046 break;
13047 }
13048 }
13049 if (bdrawn) continue; // the station loop
13050
13051 LLBBox this_box;
13052 this_box.Set(lat, lon, lat, lon);
13053 this_box.EnLarge(.005);
13054 drawn_boxes.push_back(this_box);
13055 }
13056
13057 wxPoint r;
13058 GetCanvasPointPix(lat, lon, &r);
13059 // draw standard icons
13060 if (GetVP().chart_scale > 500000) {
13061 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13062 }
13063 // draw "extended" icons
13064 else {
13065 dc.SetFont(*plabelFont);
13066 {
13067 {
13068 float val, nowlev;
13069 float ltleve = 0.;
13070 float htleve = 0.;
13071 time_t tctime;
13072 time_t lttime = 0;
13073 time_t httime = 0;
13074 bool wt;
13075 // define if flood or ebb in the last ten minutes and verify if
13076 // data are useable
13077 if (ptcmgr->GetTideFlowSens(
13078 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13079 pIDX->IDX_rec_num, nowlev, val, wt)) {
13080 // search forward the first HW or LW near "now" ( starting at
13081 // "now" - ten minutes )
13082 ptcmgr->GetHightOrLowTide(
13083 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13084 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13085 wt, pIDX->IDX_rec_num, val, tctime);
13086 if (wt) {
13087 httime = tctime;
13088 htleve = val;
13089 } else {
13090 lttime = tctime;
13091 ltleve = val;
13092 }
13093 wt = !wt;
13094
13095 // then search opposite tide near "now"
13096 if (tctime > t_this_now) // search backward
13097 ptcmgr->GetHightOrLowTide(
13098 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13099 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13100 pIDX->IDX_rec_num, val, tctime);
13101 else
13102 // or search forward
13103 ptcmgr->GetHightOrLowTide(
13104 t_this_now, FORWARD_TEN_MINUTES_STEP,
13105 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13106 val, tctime);
13107 if (wt) {
13108 httime = tctime;
13109 htleve = val;
13110 } else {
13111 lttime = tctime;
13112 ltleve = val;
13113 }
13114
13115 // draw the tide rectangle:
13116
13117 // tide icon rectangle has default pre-scaled width = 12 ,
13118 // height = 45
13119 int width = (int)(12 * scale_factor + 0.5);
13120 int height = (int)(45 * scale_factor + 0.5);
13121 int linew = wxMax(1, (int)(scale_factor));
13122 int xDraw = r.x - (width / 2);
13123 int yDraw = r.y - (height / 2);
13124
13125 // process tide state ( %height and flow sens )
13126 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13127 int hs = (httime > lttime) ? -4 : 4;
13128 hs *= (int)(scale_factor + 0.5);
13129 if (ts > 0.995 || ts < 0.005) hs = 0;
13130 int ht_y = (int)(height * ts);
13131
13132 // draw yellow tide rectangle outlined in black
13133 pblack_pen->SetWidth(linew);
13134 dc.SetPen(*pblack_pen);
13135 dc.SetBrush(*pyelo_brush);
13136 dc.DrawRectangle(xDraw, yDraw, width, height);
13137
13138 // draw blue rectangle as water height, smaller in width than
13139 // yellow rectangle
13140 dc.SetPen(*pblue_pen);
13141 dc.SetBrush(*pblue_brush);
13142 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13143 (width - (4 * linew)), height - ht_y);
13144
13145 // draw sens arrows (ensure they are not "under-drawn" by top
13146 // line of blue rectangle )
13147 int hl;
13148 wxPoint arrow[3];
13149 arrow[0].x = xDraw + 2 * linew;
13150 arrow[1].x = xDraw + width / 2;
13151 arrow[2].x = xDraw + width - 2 * linew;
13152 pyelo_pen->SetWidth(linew);
13153 pblue_pen->SetWidth(linew);
13154 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13155 {
13156 hl = (int)(height * 0.25) + yDraw;
13157 arrow[0].y = hl;
13158 arrow[1].y = hl + hs;
13159 arrow[2].y = hl;
13160 if (ts < 0.15)
13161 dc.SetPen(*pyelo_pen);
13162 else
13163 dc.SetPen(*pblue_pen);
13164 dc.DrawLines(3, arrow);
13165 }
13166 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13167 {
13168 hl = (int)(height * 0.5) + yDraw;
13169 arrow[0].y = hl;
13170 arrow[1].y = hl + hs;
13171 arrow[2].y = hl;
13172 if (ts < 0.40)
13173 dc.SetPen(*pyelo_pen);
13174 else
13175 dc.SetPen(*pblue_pen);
13176 dc.DrawLines(3, arrow);
13177 }
13178 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13179 {
13180 hl = (int)(height * 0.75) + yDraw;
13181 arrow[0].y = hl;
13182 arrow[1].y = hl + hs;
13183 arrow[2].y = hl;
13184 if (ts < 0.65)
13185 dc.SetPen(*pyelo_pen);
13186 else
13187 dc.SetPen(*pblue_pen);
13188 dc.DrawLines(3, arrow);
13189 }
13190 // draw tide level text
13191 wxString s;
13192 s.Printf(_T("%3.1f"), nowlev);
13193 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13194 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13195 int wx1;
13196 dc.GetTextExtent(s, &wx1, NULL);
13197 wx1 *= g_Platform->GetDisplayDIPMult(this);
13198 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13199 }
13200 }
13201 }
13202 }
13203 }
13204 }
13205 }
13206 }
13207}
13208
13209//------------------------------------------------------------------------------------------
13210// Currents Support
13211//------------------------------------------------------------------------------------------
13212
13213void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13214 if (!ptcmgr) return;
13215
13216 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13217
13218 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13219 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13220 double lon = pIDX->IDX_lon;
13221 double lat = pIDX->IDX_lat;
13222
13223 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13224 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13225 if ((BBox.Contains(lat, lon))) {
13226 // Manage the point selection list
13227 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13228 }
13229 }
13230 }
13231}
13232
13233void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13234 if (!ptcmgr) return;
13235
13236 float tcvalue, dir;
13237 bool bnew_val;
13238 char sbuf[20];
13239 wxFont *pTCFont;
13240 double lon_last = 0.;
13241 double lat_last = 0.;
13242 // arrow size for Raz Blanchard : 12 knots north
13243 double marge = 0.2;
13244 bool cur_time = !gTimeSource.IsValid();
13245
13246 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13247 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13248
13249 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
13250 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
13251 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13252 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )), 1,
13253 wxPENSTYLE_SOLID);
13254 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13255 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )),
13256 wxBRUSHSTYLE_SOLID);
13257 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13258 GetGlobalColor(_T ( "UIBDR" )), wxBRUSHSTYLE_SOLID);
13259 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13260 GetGlobalColor(_T ( "UINFD" )), wxBRUSHSTYLE_SOLID);
13261
13262 double skew_angle = GetVPRotation();
13263
13264 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13265 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13266 int font_size = wxMax(10, dFont->GetPointSize());
13267 font_size /= g_Platform->GetDisplayDIPMult(this);
13268 pTCFont = FontMgr::Get().FindOrCreateFont(
13269 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13270 false, dFont->GetFaceName());
13271
13272 float scale_factor = 1.0;
13273
13274 // Set the onscreen size of the symbol
13275 // Compensate for various display resolutions
13276
13277#if 0
13278 float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *3 / 1000; // Intended physical rendered size onscreen
13279 nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 2);
13280 nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 4);
13281 float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
13282#endif
13283
13284#if 0
13285 // another method is simply to declare that the icon shall be x times the size of a raster symbol (e.g.BOYLAT)
13286 // This is a bit of a hack that will suffice until until we get fully scalable ENC symbol sets
13287 float nominal_icon_size_pixels = 6; // 16 / 3
13288 float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
13289#endif
13290
13291#ifndef __ANDROID__
13292 // or, x times size of text font
13293 wxScreenDC sdc;
13294 int height;
13295 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13296 height *= g_Platform->GetDisplayDIPMult(this);
13297 float nominal_icon_size_pixels = 15;
13298 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13299
13300#else
13301 // Yet another method goes like this:
13302 // Set the onscreen size of the symbol
13303 // Compensate for various display resolutions
13304 // Develop empirically....
13305 float icon_pixelRefDim = 5;
13306
13307 double symHeight =
13308 icon_pixelRefDim /
13309 GetPixPerMM(); // from draw instructions, symbol is xx pix high
13310 double targetHeight0 = 2.0;
13311
13312 // But we want to scale the size down for smaller displays
13313 double displaySize = m_display_size_mm;
13314 displaySize = wxMax(displaySize, 100);
13315
13316 float targetHeight = wxMin(targetHeight0, displaySize / 50);
13317 double pix_factor = targetHeight / symHeight;
13318#endif
13319
13320 scale_factor *= pix_factor;
13321
13322 float user_scale_factor = g_ChartScaleFactorExp;
13323 if (g_ChartScaleFactorExp > 1.0)
13324 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13325 1.2; // soften the scale factor a bit
13326
13327 scale_factor *= user_scale_factor;
13328
13329 scale_factor *= GetContentScaleFactor();
13330
13331 {
13332 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13333 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13334 double lon = pIDX->IDX_lon;
13335 double lat = pIDX->IDX_lat;
13336
13337 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13338 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13339 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13340 wxPoint r;
13341 GetCanvasPointPix(lat, lon, &r);
13342
13343 wxPoint d[4]; // points of a diamond at the current station location
13344 int dd = (int)(5.0 * scale_factor + 0.5);
13345 d[0].x = r.x;
13346 d[0].y = r.y + dd;
13347 d[1].x = r.x + dd;
13348 d[1].y = r.y;
13349 d[2].x = r.x;
13350 d[2].y = r.y - dd;
13351 d[3].x = r.x - dd;
13352 d[3].y = r.y;
13353
13354 if (1) {
13355 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13356 dc.SetPen(*pblack_pen);
13357 dc.SetBrush(*porange_brush);
13358 dc.DrawPolygon(4, d);
13359
13360 if (type == 'C') {
13361 dc.SetBrush(*pblack_brush);
13362 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13363 }
13364
13365 if (GetVP().chart_scale < 1000000) {
13366 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13367 continue;
13368 } else
13369 continue;
13370
13371 if (1 /*type == 'c'*/) {
13372 {
13373 // Get the display pixel location of the current station
13374 int pixxc, pixyc;
13375 pixxc = r.x;
13376 pixyc = r.y;
13377
13378 // Adjust drawing size using logarithmic scale. tcvalue is
13379 // current in knots
13380 double a1 = fabs(tcvalue) * 10.;
13381 // Current values <= 0.1 knot will have no arrow
13382 a1 = wxMax(1.0, a1);
13383 double a2 = log10(a1);
13384
13385 float cscale = scale_factor * a2 * 0.4;
13386
13387 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13388 dc.SetPen(*porange_pen);
13389 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13390 cscale);
13391 // Draw text, if enabled
13392
13393 if (bDrawCurrentValues) {
13394 dc.SetFont(*pTCFont);
13395 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13396 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13397 }
13398 }
13399 } // scale
13400 }
13401 /* This is useful for debugging the TC database
13402 else
13403 {
13404 dc.SetPen ( *porange_pen );
13405 dc.SetBrush ( *pgray_brush );
13406 dc.DrawPolygon ( 4, d );
13407 }
13408 */
13409 }
13410 lon_last = lon;
13411 lat_last = lat;
13412 }
13413 }
13414 }
13415}
13416
13417void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13418 pCwin = new TCWin(this, x, y, pvIDX);
13419}
13420
13421#define NUM_CURRENT_ARROW_POINTS 9
13422static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13423 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13424 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13425 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13426
13427void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13428 double scale) {
13429 if (scale > 1e-2) {
13430 float sin_rot = sin(rot_angle * PI / 180.);
13431 float cos_rot = cos(rot_angle * PI / 180.);
13432
13433 // Move to the first point
13434
13435 float xt = CurrentArrowArray[0].x;
13436 float yt = CurrentArrowArray[0].y;
13437
13438 float xp = (xt * cos_rot) - (yt * sin_rot);
13439 float yp = (xt * sin_rot) + (yt * cos_rot);
13440 int x1 = (int)(xp * scale);
13441 int y1 = (int)(yp * scale);
13442
13443 // Walk thru the point list
13444 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13445 xt = CurrentArrowArray[ip].x;
13446 yt = CurrentArrowArray[ip].y;
13447
13448 float xp = (xt * cos_rot) - (yt * sin_rot);
13449 float yp = (xt * sin_rot) + (yt * cos_rot);
13450 int x2 = (int)(xp * scale);
13451 int y2 = (int)(yp * scale);
13452
13453 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13454
13455 x1 = x2;
13456 y1 = y2;
13457 }
13458 }
13459}
13460
13461wxString ChartCanvas::FindValidUploadPort() {
13462 wxString port;
13463 // Try to use the saved persistent upload port first
13464 if (!g_uploadConnection.IsEmpty() &&
13465 g_uploadConnection.StartsWith(_T("Serial"))) {
13466 port = g_uploadConnection;
13467 }
13468
13469 else {
13470 // If there is no persistent upload port recorded (yet)
13471 // then use the first available serial connection which has output defined.
13472 for (auto *cp : TheConnectionParams()) {
13473 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13474 port << _T("Serial:") << cp->Port;
13475 }
13476 }
13477 return port;
13478}
13479
13480void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13481 if (!win) return;
13482
13483 if (NULL == g_pais_query_dialog_active) {
13484 int pos_x = g_ais_query_dialog_x;
13485 int pos_y = g_ais_query_dialog_y;
13486
13487 if (g_pais_query_dialog_active) {
13488 g_pais_query_dialog_active->Destroy();
13489 g_pais_query_dialog_active = new AISTargetQueryDialog();
13490 } else {
13491 g_pais_query_dialog_active = new AISTargetQueryDialog();
13492 }
13493
13494 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13495 wxPoint(pos_x, pos_y));
13496
13497 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13498 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13499 g_pais_query_dialog_active->SetMMSI(mmsi);
13500 g_pais_query_dialog_active->UpdateText();
13501 wxSize sz = g_pais_query_dialog_active->GetSize();
13502
13503 bool b_reset_pos = false;
13504#ifdef __WXMSW__
13505 // Support MultiMonitor setups which an allow negative window positions.
13506 // If the requested window title bar does not intersect any installed
13507 // monitor, then default to simple primary monitor positioning.
13508 RECT frame_title_rect;
13509 frame_title_rect.left = pos_x;
13510 frame_title_rect.top = pos_y;
13511 frame_title_rect.right = pos_x + sz.x;
13512 frame_title_rect.bottom = pos_y + 30;
13513
13514 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13515 b_reset_pos = true;
13516#else
13517
13518 // Make sure drag bar (title bar) of window intersects wxClient Area of
13519 // screen, with a little slop...
13520 wxRect window_title_rect; // conservative estimate
13521 window_title_rect.x = pos_x;
13522 window_title_rect.y = pos_y;
13523 window_title_rect.width = sz.x;
13524 window_title_rect.height = 30;
13525
13526 wxRect ClientRect = wxGetClientDisplayRect();
13527 ClientRect.Deflate(
13528 60, 60); // Prevent the new window from being too close to the edge
13529 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13530
13531#endif
13532
13533 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13534
13535 } else {
13536 g_pais_query_dialog_active->SetMMSI(mmsi);
13537 g_pais_query_dialog_active->UpdateText();
13538 }
13539
13540 g_pais_query_dialog_active->Show();
13541}
13542
13543void ChartCanvas::ToggleCanvasQuiltMode(void) {
13544 bool cur_mode = GetQuiltMode();
13545
13546 if (!GetQuiltMode())
13547 SetQuiltMode(true);
13548 else if (GetQuiltMode()) {
13549 SetQuiltMode(false);
13550 g_sticky_chart = GetQuiltReferenceChartIndex();
13551 }
13552
13553 if (cur_mode != GetQuiltMode()) {
13554 SetupCanvasQuiltMode();
13555 DoCanvasUpdate();
13556 InvalidateGL();
13557 Refresh();
13558 }
13559 // TODO What to do about this?
13560 // g_bQuiltEnable = GetQuiltMode();
13561
13562 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13563 if (ps52plib) ps52plib->GenerateStateHash();
13564
13565 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13566 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13567}
13568
13569void ChartCanvas::DoCanvasStackDelta(int direction) {
13570 if (!GetQuiltMode()) {
13571 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13572 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13573 if ((current_stack_index + direction) < 0) return;
13574
13575 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13576 int new_dbIndex =
13577 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13578
13579 if (IsChartQuiltableRef(new_dbIndex)) {
13580 ToggleCanvasQuiltMode();
13581 SelectQuiltRefdbChart(new_dbIndex);
13582 m_bpersistent_quilt = false;
13583 }
13584 } else {
13585 SelectChartFromStack(current_stack_index + direction);
13586 }
13587 } else {
13588 std::vector<int> piano_chart_index_array =
13589 GetQuiltExtendedStackdbIndexArray();
13590 int refdb = GetQuiltRefChartdbIndex();
13591
13592 // Find the ref chart in the stack
13593 int current_index = -1;
13594 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13595 if (refdb == piano_chart_index_array[i]) {
13596 current_index = i;
13597 break;
13598 }
13599 }
13600 if (current_index == -1) return;
13601
13602 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13603 int target_family = ctet.GetChartFamily();
13604
13605 int new_index = -1;
13606 int check_index = current_index + direction;
13607 bool found = false;
13608 int check_dbIndex = -1;
13609 int new_dbIndex = -1;
13610
13611 // When quilted. switch within the same chart family
13612 while (!found &&
13613 (unsigned int)check_index < piano_chart_index_array.size() &&
13614 (check_index >= 0)) {
13615 check_dbIndex = piano_chart_index_array[check_index];
13616 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13617 if (target_family == cte.GetChartFamily()) {
13618 found = true;
13619 new_index = check_index;
13620 new_dbIndex = check_dbIndex;
13621 break;
13622 }
13623
13624 check_index += direction;
13625 }
13626
13627 if (!found) return;
13628
13629 if (!IsChartQuiltableRef(new_dbIndex)) {
13630 ToggleCanvasQuiltMode();
13631 SelectdbChart(new_dbIndex);
13632 m_bpersistent_quilt = true;
13633 } else {
13634 SelectQuiltRefChart(new_index);
13635 }
13636 }
13637
13638 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13639 // (checkmarks etc)
13640 SetQuiltChartHiLiteIndex(-1);
13641
13642 ReloadVP();
13643}
13644
13645//--------------------------------------------------------------------------------------------------------
13646//
13647// Toolbar support
13648//
13649//--------------------------------------------------------------------------------------------------------
13650
13651void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13652 // Handle the per-canvas toolbar clicks here
13653
13654 switch (event.GetId()) {
13655 case ID_ZOOMIN: {
13656 StopMovement();
13657 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13658 break;
13659 }
13660
13661 case ID_ZOOMOUT: {
13662 StopMovement();
13663 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13664 break;
13665 }
13666
13667 case ID_STKUP:
13668 DoCanvasStackDelta(1);
13669 DoCanvasUpdate();
13670 break;
13671
13672 case ID_STKDN:
13673 DoCanvasStackDelta(-1);
13674 DoCanvasUpdate();
13675 break;
13676
13677 case ID_FOLLOW: {
13678 TogglebFollow();
13679 break;
13680 }
13681
13682 case ID_CURRENT: {
13683 ShowCurrents(!GetbShowCurrent());
13684 ReloadVP();
13685 Refresh(false);
13686 break;
13687 }
13688
13689 case ID_TIDE: {
13690 ShowTides(!GetbShowTide());
13691 ReloadVP();
13692 Refresh(false);
13693 break;
13694 }
13695
13696 case ID_ROUTE: {
13697 if (0 == m_routeState) {
13698 StartRoute();
13699 } else {
13700 FinishRoute();
13701 }
13702
13703#ifdef __ANDROID__
13704 androidSetRouteAnnunciator(m_routeState == 1);
13705#endif
13706 break;
13707 }
13708
13709 case ID_AIS: {
13710 SetAISCanvasDisplayStyle(-1);
13711 break;
13712 }
13713
13714 default:
13715 break;
13716 }
13717
13718 // And then let gFrame handle the rest....
13719 event.Skip();
13720}
13721
13722void ChartCanvas::SetShowAIS(bool show) {
13723 m_bShowAIS = show;
13724 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13725 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13726}
13727
13728void ChartCanvas::SetAttenAIS(bool show) {
13729 m_bShowAISScaled = show;
13730 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13731 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13732}
13733
13734void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
13735 // make some arrays to hold the dfferences between cycle steps
13736 // show all, scaled, hide all
13737 bool bShowAIS_Array[3] = {true, true, false};
13738 bool bShowScaled_Array[3] = {false, true, true};
13739 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
13740 _("Attenuate less critical AIS targets"),
13741 _("Hide AIS Targets")};
13742 wxString iconName_Array[3] = {_T("AIS"), _T("AIS_Suppressed"),
13743 _T("AIS_Disabled")};
13744 int ArraySize = 3;
13745 int AIS_Toolbar_Switch = 0;
13746 if (StyleIndx == -1) { // -1 means coming from toolbar button
13747 // find current state of switch
13748 for (int i = 1; i < ArraySize; i++) {
13749 if ((bShowAIS_Array[i] == m_bShowAIS) &&
13750 (bShowScaled_Array[i] == m_bShowAISScaled))
13751 AIS_Toolbar_Switch = i;
13752 }
13753 AIS_Toolbar_Switch++; // we did click so continu with next item
13754 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
13755 AIS_Toolbar_Switch++;
13756
13757 } else { // coming from menu bar.
13758 AIS_Toolbar_Switch = StyleIndx;
13759 }
13760 // make sure we are not above array
13761 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
13762
13763 int AIS_Toolbar_Switch_Next =
13764 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
13765 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
13766 AIS_Toolbar_Switch_Next++;
13767 if (AIS_Toolbar_Switch_Next >= ArraySize)
13768 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
13769
13770 // Set found values to global and member variables
13771 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
13772 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
13773}
13774
13775void ChartCanvas::TouchAISToolActive(void) {}
13776
13777void ChartCanvas::UpdateAISTBTool(void) {}
13778
13779//---------------------------------------------------------------------------------
13780//
13781// Compass/GPS status icon support
13782//
13783//---------------------------------------------------------------------------------
13784
13785void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
13786 // Look for change in overlap or positions
13787 bool b_update = false;
13788 int cc1_edge_comp = 2;
13789 wxRect rect = m_Compass->GetRect();
13790 wxSize parent_size = GetSize();
13791
13792 parent_size *= m_displayScale;
13793
13794 // check to see if it would overlap if it was in its home position (upper
13795 // right)
13796 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
13797 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
13798 wxRect compass_rect(compass_pt, rect.GetSize());
13799
13800 m_Compass->Move(compass_pt);
13801
13802 if (m_Compass && m_Compass->IsShown())
13803 m_Compass->UpdateStatus(b_force_new | b_update);
13804
13805 wxPoint note_point =
13806 wxPoint(compass_rect.x - compass_rect.width, compass_rect.y);
13807 m_notification_button->Move(note_point);
13808
13809 m_notification_button->UpdateStatus();
13810 if (b_force_new | b_update) Refresh();
13811}
13812
13813void ChartCanvas::SelectChartFromStack(int index, bool bDir,
13814 ChartTypeEnum New_Type,
13815 ChartFamilyEnum New_Family) {
13816 if (!GetpCurrentStack()) return;
13817 if (!ChartData) return;
13818
13819 if (index < GetpCurrentStack()->nEntry) {
13820 // Open the new chart
13821 ChartBase *pTentative_Chart;
13822 pTentative_Chart = ChartData->OpenStackChartConditional(
13823 GetpCurrentStack(), index, bDir, New_Type, New_Family);
13824
13825 if (pTentative_Chart) {
13826 if (m_singleChart) m_singleChart->Deactivate();
13827
13828 m_singleChart = pTentative_Chart;
13829 m_singleChart->Activate();
13830
13831 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13832 GetpCurrentStack(), m_singleChart->GetFullPath());
13833 }
13834
13835 // Setup the view
13836 double zLat, zLon;
13837 if (m_bFollow) {
13838 zLat = gLat;
13839 zLon = gLon;
13840 } else {
13841 zLat = m_vLat;
13842 zLon = m_vLon;
13843 }
13844
13845 double best_scale_ppm = GetBestVPScale(m_singleChart);
13846 double rotation = GetVPRotation();
13847 double oldskew = GetVPSkew();
13848 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
13849
13850 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
13851 if (fabs(oldskew) > 0.0001) rotation = 0.0;
13852 if (fabs(newskew) > 0.0001) rotation = newskew;
13853 }
13854
13855 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
13856
13857 UpdateGPSCompassStatusBox(true); // Pick up the rotation
13858 }
13859
13860 // refresh Piano
13861 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
13862 if (idx < 0) return;
13863
13864 std::vector<int> piano_active_chart_index_array;
13865 piano_active_chart_index_array.push_back(
13866 GetpCurrentStack()->GetCurrentEntrydbIndex());
13867 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
13868}
13869
13870void ChartCanvas::SelectdbChart(int dbindex) {
13871 if (!GetpCurrentStack()) return;
13872 if (!ChartData) return;
13873
13874 if (dbindex >= 0) {
13875 // Open the new chart
13876 ChartBase *pTentative_Chart;
13877 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
13878
13879 if (pTentative_Chart) {
13880 if (m_singleChart) m_singleChart->Deactivate();
13881
13882 m_singleChart = pTentative_Chart;
13883 m_singleChart->Activate();
13884
13885 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13886 GetpCurrentStack(), m_singleChart->GetFullPath());
13887 }
13888
13889 // Setup the view
13890 double zLat, zLon;
13891 if (m_bFollow) {
13892 zLat = gLat;
13893 zLon = gLon;
13894 } else {
13895 zLat = m_vLat;
13896 zLon = m_vLon;
13897 }
13898
13899 double best_scale_ppm = GetBestVPScale(m_singleChart);
13900
13901 if (m_singleChart)
13902 SetViewPoint(zLat, zLon, best_scale_ppm,
13903 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
13904
13905 // SetChartUpdatePeriod( );
13906
13907 // UpdateGPSCompassStatusBox(); // Pick up the rotation
13908 }
13909
13910 // TODO refresh_Piano();
13911}
13912
13913void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
13914 double target_scale = GetVP().view_scale_ppm;
13915
13916 if (!GetQuiltMode()) {
13917 if (GetpCurrentStack()) {
13918 int stack_index = -1;
13919 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
13920 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
13921 if (check_dbIndex < 0) continue;
13922 const ChartTableEntry &cte =
13923 ChartData->GetChartTableEntry(check_dbIndex);
13924 if (type == cte.GetChartType()) {
13925 stack_index = i;
13926 break;
13927 } else if (family == cte.GetChartFamily()) {
13928 stack_index = i;
13929 break;
13930 }
13931 }
13932
13933 if (stack_index >= 0) {
13934 SelectChartFromStack(stack_index);
13935 }
13936 }
13937 } else {
13938 int sel_dbIndex = -1;
13939 std::vector<int> piano_chart_index_array =
13940 GetQuiltExtendedStackdbIndexArray();
13941 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13942 int check_dbIndex = piano_chart_index_array[i];
13943 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13944 if (type == cte.GetChartType()) {
13945 if (IsChartQuiltableRef(check_dbIndex)) {
13946 sel_dbIndex = check_dbIndex;
13947 break;
13948 }
13949 } else if (family == cte.GetChartFamily()) {
13950 if (IsChartQuiltableRef(check_dbIndex)) {
13951 sel_dbIndex = check_dbIndex;
13952 break;
13953 }
13954 }
13955 }
13956
13957 if (sel_dbIndex >= 0) {
13958 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
13959 // Re-qualify the quilt reference chart selection
13960 AdjustQuiltRefChart();
13961 }
13962
13963 // Now reset the scale to the target...
13964 SetVPScale(target_scale);
13965 }
13966
13967 SetQuiltChartHiLiteIndex(-1);
13968
13969 ReloadVP();
13970}
13971
13972bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
13973 return std::find(m_tile_yesshow_index_array.begin(),
13974 m_tile_yesshow_index_array.end(),
13975 index) != m_tile_yesshow_index_array.end();
13976}
13977
13978bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
13979 return std::find(m_tile_noshow_index_array.begin(),
13980 m_tile_noshow_index_array.end(),
13981 index) != m_tile_noshow_index_array.end();
13982}
13983
13984void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
13985 if (std::find(m_tile_noshow_index_array.begin(),
13986 m_tile_noshow_index_array.end(),
13987 index) == m_tile_noshow_index_array.end()) {
13988 m_tile_noshow_index_array.push_back(index);
13989 }
13990}
13991
13992//-------------------------------------------------------------------------------------------------------
13993//
13994// Piano support
13995//
13996//-------------------------------------------------------------------------------------------------------
13997
13998void ChartCanvas::HandlePianoClick(
13999 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14000 if (g_options && g_options->IsShown())
14001 return; // Piano might be invalid due to chartset updates.
14002 if (!m_pCurrentStack) return;
14003 if (!ChartData) return;
14004
14005 // stop movement or on slow computer we may get something like :
14006 // zoom out with the wheel (timer is set)
14007 // quickly click and display a chart, which may zoom in
14008 // but the delayed timer fires first and it zooms out again!
14009 StopMovement();
14010
14011 // When switching by piano key click, we may appoint the new target chart to
14012 // be any chart in the composite array.
14013 // As an improvement to UX, find the chart that is "closest" to the current
14014 // vp,
14015 // and select that chart. This will cause a jump to the centroid of that
14016 // chart
14017
14018 double distance = 25000; // RTW
14019 int closest_index = -1;
14020 for (int chart_index : selected_dbIndex_array) {
14021 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14022 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14023 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14024
14025 // measure distance as Manhattan style
14026 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14027 if (test_distance < distance) {
14028 distance = test_distance;
14029 closest_index = chart_index;
14030 }
14031 }
14032
14033 int selected_dbIndex = selected_dbIndex_array[0];
14034 if (closest_index >= 0) selected_dbIndex = closest_index;
14035
14036 if (!GetQuiltMode()) {
14037 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14038 if (IsChartQuiltableRef(selected_dbIndex)) {
14039 ToggleCanvasQuiltMode();
14040 SelectQuiltRefdbChart(selected_dbIndex);
14041 m_bpersistent_quilt = false;
14042 } else {
14043 SelectChartFromStack(selected_index);
14044 }
14045 } else {
14046 SelectChartFromStack(selected_index);
14047 g_sticky_chart = selected_dbIndex;
14048 }
14049
14050 if (m_singleChart)
14051 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14052 } else {
14053 // Handle MBTiles overlays first
14054 // Left click simply toggles the noshow array index entry
14055 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14056 bool bfound = false;
14057 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14058 if (m_tile_noshow_index_array[i] ==
14059 selected_dbIndex) { // chart is in the noshow list
14060 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14061 i); // erase it
14062 bfound = true;
14063 break;
14064 }
14065 }
14066 if (!bfound) {
14067 m_tile_noshow_index_array.push_back(selected_dbIndex);
14068 }
14069
14070 // If not already present, add this tileset to the "yes_show" array.
14071 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14072 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14073 }
14074
14075 else {
14076 if (IsChartQuiltableRef(selected_dbIndex)) {
14077 // if( ChartData ) ChartData->PurgeCache();
14078
14079 // If the chart is a vector chart, and of very large scale,
14080 // then we had better set the new scale directly to avoid excessive
14081 // underzoom on, eg, Inland ENCs
14082 bool set_scale = false;
14083 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14084 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14085 set_scale = true;
14086 }
14087 }
14088
14089 if (!set_scale) {
14090 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14091 } else {
14092 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14093
14094 // Adjust scale so that the selected chart is underzoomed/overzoomed
14095 // by a controlled amount
14096 ChartBase *pc =
14097 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14098 if (pc) {
14099 double proposed_scale_onscreen =
14101
14102 if (g_bPreserveScaleOnX) {
14103 proposed_scale_onscreen =
14104 wxMin(proposed_scale_onscreen,
14105 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14106 GetCanvasWidth()));
14107 } else {
14108 proposed_scale_onscreen =
14109 wxMin(proposed_scale_onscreen,
14110 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14111 GetCanvasWidth()));
14112
14113 proposed_scale_onscreen =
14114 wxMax(proposed_scale_onscreen,
14115 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14116 g_b_overzoom_x));
14117 }
14118
14119 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14120 }
14121 }
14122 } else {
14123 ToggleCanvasQuiltMode();
14124 SelectdbChart(selected_dbIndex);
14125 m_bpersistent_quilt = true;
14126 }
14127 }
14128 }
14129
14130 SetQuiltChartHiLiteIndex(-1);
14131 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14132 // (checkmarks etc)
14133 HideChartInfoWindow();
14134 DoCanvasUpdate();
14135 ReloadVP(); // Pick up the new selections
14136}
14137
14138void ChartCanvas::HandlePianoRClick(
14139 int x, int y, int selected_index,
14140 const std::vector<int> &selected_dbIndex_array) {
14141 if (g_options && g_options->IsShown())
14142 return; // Piano might be invalid due to chartset updates.
14143 if (!GetpCurrentStack()) return;
14144
14145 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14146 UpdateCanvasControlBar();
14147
14148 SetQuiltChartHiLiteIndex(-1);
14149}
14150
14151void ChartCanvas::HandlePianoRollover(
14152 int selected_index, const std::vector<int> &selected_dbIndex_array,
14153 int n_charts, int scale) {
14154 if (g_options && g_options->IsShown())
14155 return; // Piano might be invalid due to chartset updates.
14156 if (!GetpCurrentStack()) return;
14157 if (!ChartData) return;
14158
14159 if (ChartData->IsBusy()) return;
14160
14161 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14162
14163 if (!GetQuiltMode()) {
14164 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14165 } else {
14166 // Select the correct vector
14167 std::vector<int> piano_chart_index_array;
14168 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14169 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14170 if ((GetpCurrentStack()->nEntry > 1) ||
14171 (piano_chart_index_array.size() >= 1)) {
14172 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14173
14174 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14175 ReloadVP(false); // no VP adjustment allowed
14176 } else if (GetpCurrentStack()->nEntry == 1) {
14177 const ChartTableEntry &cte =
14178 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14179 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14180 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14181 ReloadVP(false);
14182 } else if ((-1 == selected_index) &&
14183 (0 == selected_dbIndex_array.size())) {
14184 ShowChartInfoWindow(key_location.x, -1);
14185 }
14186 }
14187 } else {
14188 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14189
14190 if ((GetpCurrentStack()->nEntry > 1) ||
14191 (piano_chart_index_array.size() >= 1)) {
14192 if (n_charts > 1)
14193 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14194 selected_dbIndex_array);
14195 else if (n_charts == 1)
14196 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14197
14198 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14199 ReloadVP(false); // no VP adjustment allowed
14200 }
14201 }
14202 }
14203}
14204
14205void ChartCanvas::ClearPianoRollover() {
14206 ClearQuiltChartHiLiteIndexArray();
14207 ShowChartInfoWindow(0, -1);
14208 std::vector<int> vec;
14209 ShowCompositeInfoWindow(0, 0, 0, vec);
14210 ReloadVP(false);
14211}
14212
14213void ChartCanvas::UpdateCanvasControlBar(void) {
14214 if (m_pianoFrozen) return;
14215
14216 if (!GetpCurrentStack()) return;
14217 if (!ChartData) return;
14218 if (!g_bShowChartBar) return;
14219
14220 int sel_type = -1;
14221 int sel_family = -1;
14222
14223 std::vector<int> piano_chart_index_array;
14224 std::vector<int> empty_piano_chart_index_array;
14225
14226 wxString old_hash = m_Piano->GetStoredHash();
14227
14228 if (GetQuiltMode()) {
14229 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14230 GetQuiltFullScreendbIndexArray());
14231
14232 std::vector<int> piano_active_chart_index_array =
14233 GetQuiltCandidatedbIndexArray();
14234 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14235
14236 std::vector<int> piano_eclipsed_chart_index_array =
14237 GetQuiltEclipsedStackdbIndexArray();
14238 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14239
14240 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14241 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14242
14243 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14244 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14245 } else {
14246 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14247 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14248 // TODO refresh_Piano();
14249
14250 if (m_singleChart) {
14251 sel_type = m_singleChart->GetChartType();
14252 sel_family = m_singleChart->GetChartFamily();
14253 }
14254 }
14255
14256 // Set up the TMerc and Skew arrays
14257 std::vector<int> piano_skew_chart_index_array;
14258 std::vector<int> piano_tmerc_chart_index_array;
14259 std::vector<int> piano_poly_chart_index_array;
14260
14261 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14262 const ChartTableEntry &ctei =
14263 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14264 double skew_norm = ctei.GetChartSkew();
14265 if (skew_norm > 180.) skew_norm -= 360.;
14266
14267 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14268 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14269
14270 // Polyconic skewed charts should show as skewed
14271 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14272 if (fabs(skew_norm) > 1.)
14273 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14274 else
14275 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14276 } else if (fabs(skew_norm) > 1.)
14277 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14278 }
14279 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14280 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14281 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14282
14283 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14284 if (new_hash != old_hash) {
14285 m_Piano->FormatKeys();
14286 HideChartInfoWindow();
14287 m_Piano->ResetRollover();
14288 SetQuiltChartHiLiteIndex(-1);
14289 m_brepaint_piano = true;
14290 }
14291
14292 // Create a bitmask int that describes what Family/Type of charts are shown in
14293 // the bar, and notify the platform.
14294 int mask = 0;
14295 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14296 const ChartTableEntry &ctei =
14297 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14298 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14299 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14300 if (e == CHART_FAMILY_RASTER) mask |= 1;
14301 if (e == CHART_FAMILY_VECTOR) {
14302 if (t == CHART_TYPE_CM93COMP)
14303 mask |= 4;
14304 else
14305 mask |= 2;
14306 }
14307 }
14308
14309 wxString s_indicated;
14310 if (sel_type == CHART_TYPE_CM93COMP)
14311 s_indicated = _T("cm93");
14312 else {
14313 if (sel_family == CHART_FAMILY_RASTER)
14314 s_indicated = _T("raster");
14315 else if (sel_family == CHART_FAMILY_VECTOR)
14316 s_indicated = _T("vector");
14317 }
14318
14319 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14320}
14321
14322void ChartCanvas::FormatPianoKeys(void) { m_Piano->FormatKeys(); }
14323
14324void ChartCanvas::PianoPopupMenu(
14325 int x, int y, int selected_index,
14326 const std::vector<int> &selected_dbIndex_array) {
14327 if (!GetpCurrentStack()) return;
14328
14329 // No context menu if quilting is disabled
14330 if (!GetQuiltMode()) return;
14331
14332 m_piano_ctx_menu = new wxMenu();
14333
14334 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14335 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14336 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14337 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14338 } else {
14339 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14340 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14341 // wxEVT_COMMAND_MENU_SELECTED,
14342 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14343
14344 menu_selected_dbIndex = selected_dbIndex_array[0];
14345 menu_selected_index = selected_index;
14346
14347 // Search the no-show array
14348 bool b_is_in_noshow = false;
14349 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14350 if (m_quilt_noshow_index_array[i] ==
14351 menu_selected_dbIndex) // chart is in the noshow list
14352 {
14353 b_is_in_noshow = true;
14354 break;
14355 }
14356 }
14357
14358 if (b_is_in_noshow) {
14359 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14360 _("Show This Chart"));
14361 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14362 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14363 } else if (GetpCurrentStack()->nEntry > 1) {
14364 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14365 _("Hide This Chart"));
14366 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14367 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14368 }
14369 }
14370
14371 wxPoint pos = wxPoint(x, y - 30);
14372
14373 // Invoke the drop-down menu
14374 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14375 PopupMenu(m_piano_ctx_menu, pos);
14376
14377 delete m_piano_ctx_menu;
14378 m_piano_ctx_menu = NULL;
14379
14380 HideChartInfoWindow();
14381 m_Piano->ResetRollover();
14382
14383 SetQuiltChartHiLiteIndex(-1);
14384 ClearQuiltChartHiLiteIndexArray();
14385
14386 ReloadVP();
14387}
14388
14389void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14390 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14391 if (m_quilt_noshow_index_array[i] ==
14392 menu_selected_dbIndex) // chart is in the noshow list
14393 {
14394 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14395 break;
14396 }
14397 }
14398}
14399
14400void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14401 if (!GetpCurrentStack()) return;
14402 if (!ChartData) return;
14403
14404 RemoveChartFromQuilt(menu_selected_dbIndex);
14405
14406 // It could happen that the chart being disabled is the reference
14407 // chart....
14408 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14409 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14410
14411 int i = menu_selected_index + 1; // select next smaller scale chart
14412 bool b_success = false;
14413 while (i < GetpCurrentStack()->nEntry - 1) {
14414 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14415 if (type == ChartData->GetDBChartType(dbIndex)) {
14416 SelectQuiltRefChart(i);
14417 b_success = true;
14418 break;
14419 }
14420 i++;
14421 }
14422
14423 // If that did not work, try to select the next larger scale compatible
14424 // chart
14425 if (!b_success) {
14426 i = menu_selected_index - 1;
14427 while (i > 0) {
14428 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14429 if (type == ChartData->GetDBChartType(dbIndex)) {
14430 SelectQuiltRefChart(i);
14431 b_success = true;
14432 break;
14433 }
14434 i--;
14435 }
14436 }
14437 }
14438}
14439
14440void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14441 // Remove the item from the list (if it appears) to avoid multiple addition
14442 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14443 if (m_quilt_noshow_index_array[i] ==
14444 dbIndex) // chart is already in the noshow list
14445 {
14446 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14447 break;
14448 }
14449 }
14450
14451 m_quilt_noshow_index_array.push_back(dbIndex);
14452}
14453
14454bool ChartCanvas::UpdateS52State() {
14455 bool retval = false;
14456 // printf(" update %d\n", IsPrimaryCanvas());
14457
14458 if (ps52plib) {
14459 ps52plib->SetShowS57Text(m_encShowText);
14460 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14461 ps52plib->m_bShowSoundg = m_encShowDepth;
14462 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14463 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14464
14465 // Lights
14466 if (!m_encShowLights) // On, going off
14467 ps52plib->AddObjNoshow("LIGHTS");
14468 else // Off, going on
14469 ps52plib->RemoveObjNoshow("LIGHTS");
14470 ps52plib->SetLightsOff(!m_encShowLights);
14471 ps52plib->m_bExtendLightSectors = true;
14472
14473 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14474 ps52plib->SetAnchorOn(m_encShowAnchor);
14475 ps52plib->SetQualityOfData(m_encShowDataQual);
14476 }
14477
14478 return retval;
14479}
14480
14481void ChartCanvas::SetShowENCDataQual(bool show) {
14482 m_encShowDataQual = show;
14483 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14484 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14485
14486 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14487}
14488
14489void ChartCanvas::SetShowENCText(bool show) {
14490 m_encShowText = show;
14491 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14492 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14493
14494 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14495}
14496
14497void ChartCanvas::SetENCDisplayCategory(int category) {
14498 m_encDisplayCategory = category;
14499 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14500}
14501
14502void ChartCanvas::SetShowENCDepth(bool show) {
14503 m_encShowDepth = show;
14504 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14505 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14506
14507 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14508}
14509
14510void ChartCanvas::SetShowENCLightDesc(bool show) {
14511 m_encShowLightDesc = show;
14512 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14513 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14514
14515 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14516}
14517
14518void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14519 m_encShowBuoyLabels = show;
14520 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14521}
14522
14523void ChartCanvas::SetShowENCLights(bool show) {
14524 m_encShowLights = show;
14525 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14526 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14527
14528 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14529}
14530
14531void ChartCanvas::SetShowENCAnchor(bool show) {
14532 m_encShowAnchor = show;
14533 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14534 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14535
14536 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14537}
14538
14539wxRect ChartCanvas::GetMUIBarRect() {
14540 wxRect rv;
14541 if (m_muiBar) {
14542 rv = m_muiBar->GetRect();
14543 }
14544
14545 return rv;
14546}
14547
14548void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14549 if (!GetAlertString().IsEmpty()) {
14550 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14551 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14552
14553 dc.SetFont(*pfont);
14554 dc.SetPen(*wxTRANSPARENT_PEN);
14555
14556 dc.SetBrush(wxColour(243, 229, 47));
14557 int w, h;
14558 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14559 h += 2;
14560 // int yp = vp.pix_height - 20 - h;
14561
14562 wxRect sbr = GetScaleBarRect();
14563 int xp = sbr.x + sbr.width + 10;
14564 int yp = (sbr.y + sbr.height) - h;
14565
14566 int wdraw = w + 10;
14567 dc.DrawRectangle(xp, yp, wdraw, h);
14568 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14569 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14570 }
14571}
14572
14573//--------------------------------------------------------------------------------------------------------
14574// Screen Brightness Control Support Routines
14575//
14576//--------------------------------------------------------------------------------------------------------
14577
14578#ifdef __UNIX__
14579#define BRIGHT_XCALIB
14580#define __OPCPN_USEICC__
14581#endif
14582
14583#ifdef __OPCPN_USEICC__
14584int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14585 double co_green, double co_blue);
14586
14587wxString temp_file_name;
14588#endif
14589
14590#if 0
14591class ocpnCurtain: public wxDialog
14592{
14593 DECLARE_CLASS( ocpnCurtain )
14594 DECLARE_EVENT_TABLE()
14595
14596public:
14597 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14598 ~ocpnCurtain( );
14599 bool ProcessEvent(wxEvent& event);
14600
14601};
14602
14603IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14604
14605BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14606END_EVENT_TABLE()
14607
14608ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14609{
14610 wxDialog::Create( parent, -1, _T("ocpnCurtain"), position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14611}
14612
14613ocpnCurtain::~ocpnCurtain()
14614{
14615}
14616
14617bool ocpnCurtain::ProcessEvent(wxEvent& event)
14618{
14619 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14620 return GetParent()->GetEventHandler()->ProcessEvent(event);
14621}
14622#endif
14623
14624#ifdef _WIN32
14625#include <windows.h>
14626
14627HMODULE hGDI32DLL;
14628typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14629typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14630SetDeviceGammaRamp_ptr_type
14631 g_pSetDeviceGammaRamp; // the API entry points in the dll
14632GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14633
14634WORD *g_pSavedGammaMap;
14635
14636#endif
14637
14638int InitScreenBrightness(void) {
14639#ifdef _WIN32
14640#ifdef ocpnUSE_GL
14641 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14642 HDC hDC;
14643 BOOL bbr;
14644
14645 if (NULL == hGDI32DLL) {
14646 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14647
14648 if (NULL != hGDI32DLL) {
14649 // Get the entry points of the required functions
14650 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14651 hGDI32DLL, "SetDeviceGammaRamp");
14652 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14653 hGDI32DLL, "GetDeviceGammaRamp");
14654
14655 // If the functions are not found, unload the DLL and return false
14656 if ((NULL == g_pSetDeviceGammaRamp) ||
14657 (NULL == g_pGetDeviceGammaRamp)) {
14658 FreeLibrary(hGDI32DLL);
14659 hGDI32DLL = NULL;
14660 return 0;
14661 }
14662 }
14663 }
14664
14665 // Interface is ready, so....
14666 // Get some storage
14667 if (!g_pSavedGammaMap) {
14668 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14669
14670 hDC = GetDC(NULL); // Get the full screen DC
14671 bbr = g_pGetDeviceGammaRamp(
14672 hDC, g_pSavedGammaMap); // Get the existing ramp table
14673 ReleaseDC(NULL, hDC); // Release the DC
14674 }
14675
14676 // On Windows hosts, try to adjust the registry to allow full range
14677 // setting of Gamma table This is an undocumented Windows hack.....
14678 wxRegKey *pRegKey = new wxRegKey(
14679 _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows ")
14680 _T("NT\\CurrentVersion\\ICM"));
14681 if (!pRegKey->Exists()) pRegKey->Create();
14682 pRegKey->SetValue(_T("GdiIcmGammaRange"), 256);
14683
14684 g_brightness_init = true;
14685 return 1;
14686 }
14687#endif
14688
14689 {
14690 if (NULL == g_pcurtain) {
14691 if (gFrame->CanSetTransparent()) {
14692 // Build the curtain window
14693 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, _T(""),
14694 wxPoint(0, 0), ::wxGetDisplaySize(),
14695 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14696 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14697
14698 // g_pcurtain = new ocpnCurtain(gFrame,
14699 // wxPoint(0,0),::wxGetDisplaySize(),
14700 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14701 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14702
14703 g_pcurtain->Hide();
14704
14705 HWND hWnd = GetHwndOf(g_pcurtain);
14706 SetWindowLong(hWnd, GWL_EXSTYLE,
14707 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14708 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14709 g_pcurtain->SetTransparent(0);
14710
14711 g_pcurtain->Maximize();
14712 g_pcurtain->Show();
14713
14714 // All of this is obtuse, but necessary for Windows...
14715 g_pcurtain->Enable();
14716 g_pcurtain->Disable();
14717
14718 gFrame->Disable();
14719 gFrame->Enable();
14720 // SetFocus();
14721 }
14722 }
14723 g_brightness_init = true;
14724
14725 return 1;
14726 }
14727#else
14728 // Look for "xcalib" application
14729 wxString cmd(_T ( "xcalib -version" ));
14730
14731 wxArrayString output;
14732 long r = wxExecute(cmd, output);
14733 if (0 != r)
14734 wxLogMessage(
14735 _T(" External application \"xcalib\" not found. Screen brightness ")
14736 _T("not changed."));
14737
14738 g_brightness_init = true;
14739 return 0;
14740#endif
14741}
14742
14743int RestoreScreenBrightness(void) {
14744#ifdef _WIN32
14745
14746 if (g_pSavedGammaMap) {
14747 HDC hDC = GetDC(NULL); // Get the full screen DC
14748 g_pSetDeviceGammaRamp(hDC,
14749 g_pSavedGammaMap); // Restore the saved ramp table
14750 ReleaseDC(NULL, hDC); // Release the DC
14751
14752 free(g_pSavedGammaMap);
14753 g_pSavedGammaMap = NULL;
14754 }
14755
14756 if (g_pcurtain) {
14757 g_pcurtain->Close();
14758 g_pcurtain->Destroy();
14759 g_pcurtain = NULL;
14760 }
14761
14762 g_brightness_init = false;
14763 return 1;
14764
14765#endif
14766
14767#ifdef BRIGHT_XCALIB
14768 if (g_brightness_init) {
14769 wxString cmd;
14770 cmd = _T("xcalib -clear");
14771 wxExecute(cmd, wxEXEC_ASYNC);
14772 g_brightness_init = false;
14773 }
14774
14775 return 1;
14776#endif
14777
14778 return 0;
14779}
14780
14781// Set brightness. [0..100]
14782int SetScreenBrightness(int brightness) {
14783#ifdef _WIN32
14784
14785 // Under Windows, we use the SetDeviceGammaRamp function which exists in
14786 // some (most modern?) versions of gdi32.dll Load the required library dll,
14787 // if not already in place
14788#ifdef ocpnUSE_GL
14789 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14790 if (g_pcurtain) {
14791 g_pcurtain->Close();
14792 g_pcurtain->Destroy();
14793 g_pcurtain = NULL;
14794 }
14795
14796 InitScreenBrightness();
14797
14798 if (NULL == hGDI32DLL) {
14799 // Unicode stuff.....
14800 wchar_t wdll_name[80];
14801 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
14802 LPCWSTR cstr = wdll_name;
14803
14804 hGDI32DLL = LoadLibrary(cstr);
14805
14806 if (NULL != hGDI32DLL) {
14807 // Get the entry points of the required functions
14808 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14809 hGDI32DLL, "SetDeviceGammaRamp");
14810 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14811 hGDI32DLL, "GetDeviceGammaRamp");
14812
14813 // If the functions are not found, unload the DLL and return false
14814 if ((NULL == g_pSetDeviceGammaRamp) ||
14815 (NULL == g_pGetDeviceGammaRamp)) {
14816 FreeLibrary(hGDI32DLL);
14817 hGDI32DLL = NULL;
14818 return 0;
14819 }
14820 }
14821 }
14822
14823 HDC hDC = GetDC(NULL); // Get the full screen DC
14824
14825 /*
14826 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
14827 if (cmcap != CM_GAMMA_RAMP)
14828 {
14829 wxLogMessage(_T(" Video hardware does not support brightness control by
14830 gamma ramp adjustment.")); return false;
14831 }
14832 */
14833
14834 int increment = brightness * 256 / 100;
14835
14836 // Build the Gamma Ramp table
14837 WORD GammaTable[3][256];
14838
14839 int table_val = 0;
14840 for (int i = 0; i < 256; i++) {
14841 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
14842 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
14843 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
14844
14845 table_val += increment;
14846
14847 if (table_val > 65535) table_val = 65535;
14848 }
14849
14850 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
14851 ReleaseDC(NULL, hDC); // Release the DC
14852
14853 return 1;
14854 }
14855#endif
14856
14857 {
14858 if (g_pSavedGammaMap) {
14859 HDC hDC = GetDC(NULL); // Get the full screen DC
14860 g_pSetDeviceGammaRamp(hDC,
14861 g_pSavedGammaMap); // Restore the saved ramp table
14862 ReleaseDC(NULL, hDC); // Release the DC
14863 }
14864
14865 if (brightness < 100) {
14866 if (NULL == g_pcurtain) InitScreenBrightness();
14867
14868 if (g_pcurtain) {
14869 int sbrite = wxMax(1, brightness);
14870 sbrite = wxMin(100, sbrite);
14871
14872 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
14873 }
14874 } else {
14875 if (g_pcurtain) {
14876 g_pcurtain->Close();
14877 g_pcurtain->Destroy();
14878 g_pcurtain = NULL;
14879 }
14880 }
14881
14882 return 1;
14883 }
14884
14885#endif
14886
14887#ifdef BRIGHT_XCALIB
14888
14889 if (!g_brightness_init) {
14890 last_brightness = 100;
14891 g_brightness_init = true;
14892 temp_file_name = wxFileName::CreateTempFileName(_T(""));
14893 InitScreenBrightness();
14894 }
14895
14896#ifdef __OPCPN_USEICC__
14897 // Create a dead simple temporary ICC profile file, with gamma ramps set as
14898 // desired, and then activate this temporary profile using xcalib <filename>
14899 if (!CreateSimpleICCProfileFile(
14900 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
14901 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
14902 wxString cmd(_T ( "xcalib " ));
14903 cmd += temp_file_name;
14904
14905 wxExecute(cmd, wxEXEC_ASYNC);
14906 }
14907
14908#else
14909 // Or, use "xcalib -co" to set overall contrast value
14910 // This is not as nice, since the -co parameter wants to be a fraction of
14911 // the current contrast, and values greater than 100 are not allowed. As a
14912 // result, increases of contrast must do a "-clear" step first, which
14913 // produces objectionable flashing.
14914 if (brightness > last_brightness) {
14915 wxString cmd;
14916 cmd = _T("xcalib -clear");
14917 wxExecute(cmd, wxEXEC_ASYNC);
14918
14919 ::wxMilliSleep(10);
14920
14921 int brite_adj = wxMax(1, brightness);
14922 cmd.Printf(_T("xcalib -co %2d -a"), brite_adj);
14923 wxExecute(cmd, wxEXEC_ASYNC);
14924 } else {
14925 int brite_adj = wxMax(1, brightness);
14926 int factor = (brite_adj * 100) / last_brightness;
14927 factor = wxMax(1, factor);
14928 wxString cmd;
14929 cmd.Printf(_T("xcalib -co %2d -a"), factor);
14930 wxExecute(cmd, wxEXEC_ASYNC);
14931 }
14932
14933#endif
14934
14935 last_brightness = brightness;
14936
14937#endif
14938
14939 return 0;
14940}
14941
14942#ifdef __OPCPN_USEICC__
14943
14944#define MLUT_TAG 0x6d4c5554L
14945#define VCGT_TAG 0x76636774L
14946
14947int GetIntEndian(unsigned char *s) {
14948 int ret;
14949 unsigned char *p;
14950 int i;
14951
14952 p = (unsigned char *)&ret;
14953
14954 if (1)
14955 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
14956 else
14957 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
14958
14959 return ret;
14960}
14961
14962unsigned short GetShortEndian(unsigned char *s) {
14963 unsigned short ret;
14964 unsigned char *p;
14965 int i;
14966
14967 p = (unsigned char *)&ret;
14968
14969 if (1)
14970 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
14971 else
14972 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
14973
14974 return ret;
14975}
14976
14977// Create a very simple Gamma correction file readable by xcalib
14978int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14979 double co_green, double co_blue) {
14980 FILE *fp;
14981
14982 if (file_name) {
14983 fp = fopen(file_name, "wb");
14984 if (!fp) return -1; /* file can not be created */
14985 } else
14986 return -1; /* filename char pointer not valid */
14987
14988 // Write header
14989 char header[128];
14990 for (int i = 0; i < 128; i++) header[i] = 0;
14991
14992 fwrite(header, 128, 1, fp);
14993
14994 // Num tags
14995 int numTags0 = 1;
14996 int numTags = GetIntEndian((unsigned char *)&numTags0);
14997 fwrite(&numTags, 1, 4, fp);
14998
14999 int tagName0 = VCGT_TAG;
15000 int tagName = GetIntEndian((unsigned char *)&tagName0);
15001 fwrite(&tagName, 1, 4, fp);
15002
15003 int tagOffset0 = 128 + 4 * sizeof(int);
15004 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15005 fwrite(&tagOffset, 1, 4, fp);
15006
15007 int tagSize0 = 1;
15008 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15009 fwrite(&tagSize, 1, 4, fp);
15010
15011 fwrite(&tagName, 1, 4, fp); // another copy of tag
15012
15013 fwrite(&tagName, 1, 4, fp); // dummy
15014
15015 // Table type
15016
15017 /* VideoCardGammaTable (The simplest type) */
15018 int gammatype0 = 0;
15019 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15020 fwrite(&gammatype, 1, 4, fp);
15021
15022 int numChannels0 = 3;
15023 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15024 fwrite(&numChannels, 1, 2, fp);
15025
15026 int numEntries0 = 256;
15027 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15028 fwrite(&numEntries, 1, 2, fp);
15029
15030 int entrySize0 = 1;
15031 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15032 fwrite(&entrySize, 1, 2, fp);
15033
15034 unsigned char ramp[256];
15035
15036 // Red ramp
15037 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15038 fwrite(ramp, 256, 1, fp);
15039
15040 // Green ramp
15041 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15042 fwrite(ramp, 256, 1, fp);
15043
15044 // Blue ramp
15045 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15046 fwrite(ramp, 256, 1, fp);
15047
15048 fclose(fp);
15049
15050 return 0;
15051}
15052#endif // __OPCPN_USEICC__
bool g_bresponsive
Flag to control adaptive UI scaling.
Definition ocpn_app.cpp:661
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:226
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:4569
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4565
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:4515
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:461
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:492
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2402
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7574
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5066
float GetVPScale()
Return the ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:450
void DoZoomCanvas(double factor, bool can_zoom_to_cursor=true)
Internal function that implements the actual zoom operation.
Definition chcanv.cpp:4671
double GetCanvasTrueScale()
Return the physical pixels per meter at chart center, accounting for latitude distortion.
Definition chcanv.h:466
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4646
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5345
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:4590
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:4651
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4510
bool SetViewPoint(double lat, double lon)
Set the viewport center point.
Definition chcanv.cpp:5364
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:9894
Manages the chart database and provides access to chart data.
Definition chartdb.h:95
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:464
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:392
Primary navigation console display for route and vessel tracking.
Definition concanv.h:127
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition FontMgr.cpp:450
wxColour GetFontColor(const wxString &TextElement) const
Gets the text color for a UI element.
Definition FontMgr.cpp:117
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Gets a font object for a UI element.
Definition FontMgr.cpp:186
Modal dialog displaying hotkeys.
Definition hotkeys_dlg.h:29
Represents an index entry for tidal and current data.
Definition IDX_entry.h:49
char IDX_type
Entry type identifier "TCtcIUu".
Definition IDX_entry.h:61
double IDX_lat
Latitude of the station (in degrees, +North)
Definition IDX_entry.h:65
double IDX_lon
Longitude of the station (in degrees, +East)
Definition IDX_entry.h:64
bool b_skipTooDeep
Flag to skip processing if depth exceeds limit.
Definition IDX_entry.h:110
Station_Data * pref_sta_data
Pointer to the reference station data.
Definition IDX_entry.h:97
int IDX_rec_num
Record number for multiple entries with same name.
Definition IDX_entry.h:60
Definition kml.h:54
Modern User Interface Control Bar for OpenCPN.
Definition MUIBar.h:63
Dialog for displaying and editing waypoint properties.
Definition MarkInfo.h:212
Main application frame.
Definition ocpn_frame.h:136
wxRect GetLogicalRect(void) const
Return the coordinates of the widget, in logical pixels.
wxRect GetRect(void) const
Return the coordinates of the widget, in physical pixels relative to the canvas window.
Provides platform-specific support utilities for OpenCPN.
wxSize getDisplaySize()
Get the display size in logical pixels.
double GetDisplaySizeMM()
Get the width of the screen in millimeters.
An iterator class for OCPNRegion.
Definition OCPNRegion.h:156
A wrapper class for wxRegion with additional functionality.
Definition OCPNRegion.h:56
Definition piano.h:65
A popup frame containing a detail slider for chart display.
Definition Quilt.h:83
bool Compose(const ViewPort &vp)
Definition Quilt.cpp:1695
Represents a waypoint or mark within the navigation system.
Definition route_point.h:70
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
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:174
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:394
bool DeleteRoute(Route *pRoute, NavObjectChanges *nav_obj_changes)
Definition routeman.cpp:835
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:86
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:139
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:145
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 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.
void EnableTenHertzUpdate(bool enable)
Enable or disable 10 Hz update rate.
bool GetEnableTenHertzUpdate()
Check if 10 Hz update rate is enabled.
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