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;
315
316// TODO why are these static?
317
327static int mouse_x;
337static int mouse_y;
338static bool mouse_leftisdown;
339
340bool g_brouteCreating;
341
342bool g_bShowTrackPointTime;
343
344int r_gamma_mult;
345int g_gamma_mult;
346int b_gamma_mult;
347int gamma_state;
348bool g_brightness_init;
349int last_brightness;
350
351int g_cog_predictor_width;
352extern double g_display_size_mm;
353
354extern ocpnFloatingToolbarDialog *g_MainToolbar;
355extern iENCToolbar *g_iENCToolbar;
356extern wxColour g_colourOwnshipRangeRingsColour;
357
358// LIVE ETA OPTION
359bool g_bShowLiveETA;
360extern double g_defaultBoatSpeed;
361double g_defaultBoatSpeedUserUnit;
362
363extern int g_nAIS_activity_timer;
364extern bool g_bskew_comp;
365extern float g_compass_scalefactor;
366extern int g_COGAvgSec; // COG average period (sec.) for Course Up Mode
367extern bool g_btenhertz;
368
369wxGLContext *g_pGLcontext; // shared common context
370
371extern bool g_useMUI;
372extern unsigned int g_canvasConfig;
373
374extern ChartCanvas *g_focusCanvas;
375extern ChartCanvas *g_overlayCanvas;
376
377extern float g_toolbar_scalefactor;
378extern SENCThreadManager *g_SencThreadManager;
379
380wxString g_ObjQFileExt;
381
382// "Curtain" mode parameters
383wxDialog *g_pcurtain;
384
385extern int g_GUIScaleFactor;
386// Win DPI scale factor
387double g_scaler;
388wxString g_lastS52PLIBPluginMessage;
389extern bool g_bChartBarEx;
390bool g_PrintingInProgress;
391
392#define MIN_BRIGHT 10
393#define MAX_BRIGHT 100
394
395//------------------------------------------------------------------------------
396// ChartCanvas Implementation
397//------------------------------------------------------------------------------
398BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
399EVT_PAINT(ChartCanvas::OnPaint)
400EVT_ACTIVATE(ChartCanvas::OnActivate)
401EVT_SIZE(ChartCanvas::OnSize)
402#ifndef HAVE_WX_GESTURE_EVENTS
403EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
404#endif
405EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
406EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
407EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
408EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
409EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
410EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
411EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
412EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
413EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
414EVT_KEY_UP(ChartCanvas::OnKeyUp)
415EVT_CHAR(ChartCanvas::OnKeyChar)
416EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
417EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
418EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
419EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
420EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
421EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
422EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
423EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
424
425END_EVENT_TABLE()
426
427// Define a constructor for my canvas
428ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
429 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
430 m_nmea_log(nmea_log) {
431 parent_frame = (MyFrame *)frame; // save a pointer to parent
432 m_canvasIndex = canvasIndex;
433
434 pscratch_bm = NULL;
435
436 SetBackgroundColour(wxColour(0, 0, 0));
437 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
438 // color scheme change
439
440 m_groupIndex = 0;
441 m_bDrawingRoute = false;
442 m_bRouteEditing = false;
443 m_bMarkEditing = false;
444 m_bRoutePoinDragging = false;
445 m_bIsInRadius = false;
446 m_bMayToggleMenuBar = true;
447
448 m_bFollow = false;
449 m_bShowNavobjects = true;
450 m_bTCupdate = false;
451 m_bAppendingRoute = false; // was true in MSW, why??
452 pThumbDIBShow = NULL;
453 m_bShowCurrent = false;
454 m_bShowTide = false;
455 bShowingCurrent = false;
456 pCwin = NULL;
457 warp_flag = false;
458 m_bzooming = false;
459 m_b_paint_enable = true;
460 m_routeState = 0;
461
462 pss_overlay_bmp = NULL;
463 pss_overlay_mask = NULL;
464 m_bChartDragging = false;
465 m_bMeasure_Active = false;
466 m_bMeasure_DistCircle = false;
467 m_pMeasureRoute = NULL;
468 m_pTrackRolloverWin = NULL;
469 m_pRouteRolloverWin = NULL;
470 m_pAISRolloverWin = NULL;
471 m_bedge_pan = false;
472 m_disable_edge_pan = false;
473 m_dragoffsetSet = false;
474 m_bautofind = false;
475 m_bFirstAuto = true;
476 m_groupIndex = 0;
477 m_singleChart = NULL;
478 m_upMode = NORTH_UP_MODE;
479 m_bShowAIS = true;
480 m_bShowAISScaled = false;
481 m_timed_move_vp_active = false;
482
483 m_vLat = 0.;
484 m_vLon = 0.;
485
486 m_pCIWin = NULL;
487
488 m_pSelectedRoute = NULL;
489 m_pSelectedTrack = NULL;
490 m_pRoutePointEditTarget = NULL;
491 m_pFoundPoint = NULL;
492 m_pMouseRoute = NULL;
493 m_prev_pMousePoint = NULL;
494 m_pEditRouteArray = NULL;
495 m_pFoundRoutePoint = NULL;
496 m_FinishRouteOnKillFocus = true;
497
498 m_pRolloverRouteSeg = NULL;
499 m_pRolloverTrackSeg = NULL;
500 m_bsectors_shown = false;
501
502 m_bbrightdir = false;
503 r_gamma_mult = 1;
504 g_gamma_mult = 1;
505 b_gamma_mult = 1;
506
507 m_pos_image_user_day = NULL;
508 m_pos_image_user_dusk = NULL;
509 m_pos_image_user_night = NULL;
510 m_pos_image_user_grey_day = NULL;
511 m_pos_image_user_grey_dusk = NULL;
512 m_pos_image_user_grey_night = NULL;
513
514 m_zoom_factor = 1;
515 m_rotation_speed = 0;
516 m_mustmove = 0;
517
518 m_OSoffsetx = 0.;
519 m_OSoffsety = 0.;
520
521 m_pos_image_user_yellow_day = NULL;
522 m_pos_image_user_yellow_dusk = NULL;
523 m_pos_image_user_yellow_night = NULL;
524
525 SetOwnShipState(SHIP_INVALID);
526
527 undo = new Undo(this);
528
529 VPoint.Invalidate();
530
531 m_glcc = NULL;
532
533 m_focus_indicator_pix = 1;
534
535 m_pCurrentStack = NULL;
536 m_bpersistent_quilt = false;
537 m_piano_ctx_menu = NULL;
538 m_Compass = NULL;
539 m_NotificationsList = NULL;
540 m_notification_button = NULL;
541
542 g_ChartNotRenderScaleFactor = 2.0;
543 m_bShowScaleInStatusBar = true;
544
545 m_muiBar = NULL;
546 m_bShowScaleInStatusBar = false;
547 m_show_focus_bar = true;
548
549 m_bShowOutlines = false;
550 m_bDisplayGrid = false;
551 m_bShowDepthUnits = true;
552 m_encDisplayCategory = (int)STANDARD;
553
554 m_encShowLights = true;
555 m_encShowAnchor = true;
556 m_encShowDataQual = false;
557 m_bShowGPS = true;
558 m_pQuilt = new Quilt(this);
559 SetQuiltMode(true);
560 SetAlertString(_T(""));
561 m_sector_glat = 0;
562 m_sector_glon = 0;
563 g_PrintingInProgress = false;
564
565#ifdef HAVE_WX_GESTURE_EVENTS
566 m_oldVPSScale = -1.0;
567 m_popupWanted = false;
568 m_leftdown = false;
569#endif /* HAVE_WX_GESTURE_EVENTS */
570
571 SetupGlCanvas();
572
573 singleClickEventIsValid = false;
574
575 // Build the cursors
576
577 pCursorLeft = NULL;
578 pCursorRight = NULL;
579 pCursorUp = NULL;
580 pCursorDown = NULL;
581 pCursorArrow = NULL;
582 pCursorPencil = NULL;
583 pCursorCross = NULL;
584
585 RebuildCursors();
586
587 SetCursor(*pCursorArrow);
588
589 pPanTimer = new wxTimer(this, m_MouseDragging);
590 pPanTimer->Stop();
591
592 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
593 pMovementTimer->Stop();
594
595 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
596 pMovementStopTimer->Stop();
597
598 pRotDefTimer = new wxTimer(this, ROT_TIMER);
599 pRotDefTimer->Stop();
600
601 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
602 m_DoubleClickTimer->Stop();
603
604 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
605 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
606 m_chart_drag_inertia_active = false;
607
608 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
609 m_animationActive = false;
610
611 m_panx = m_pany = 0;
612 m_panspeed = 0;
613 m_panx_target_final = m_pany_target_final = 0;
614 m_panx_target_now = m_pany_target_now = 0;
615
616 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
617 pCurTrackTimer->Stop();
618 m_curtrack_timer_msec = 10;
619
620 m_wheelzoom_stop_oneshot = 0;
621 m_last_wheel_dir = 0;
622
623 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
624
625 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
626
627 m_rollover_popup_timer_msec = 20;
628
629 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
630
631 m_b_rot_hidef = true;
632
633 proute_bm = NULL;
634 m_prot_bm = NULL;
635
636 m_upMode = NORTH_UP_MODE;
637 m_bLookAhead = false;
638
639 // Set some benign initial values
640
641 m_cs = GLOBAL_COLOR_SCHEME_DAY;
642 VPoint.clat = 0;
643 VPoint.clon = 0;
644 VPoint.view_scale_ppm = 1;
645 VPoint.Invalidate();
646 m_nMeasureState = 0;
647
648 m_canvas_scale_factor = 1.;
649
650 m_canvas_width = 1000;
651
652 m_overzoomTextWidth = 0;
653 m_overzoomTextHeight = 0;
654
655 // Create the default world chart
656 pWorldBackgroundChart = new GSHHSChart;
657 gShapeBasemap.Reset();
658
659 // Create the default depth unit emboss maps
660 m_pEM_Feet = NULL;
661 m_pEM_Meters = NULL;
662 m_pEM_Fathoms = NULL;
663
664 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
665
666 m_pEM_OverZoom = NULL;
667 SetOverzoomFont();
668 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
669
670 // Build icons for tide/current points
671 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
672 m_bmTideDay = style->GetIconScaled(_T("tidesml"),
673 1. / g_Platform->GetDisplayDIPMult(this));
674
675 // Dusk
676 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
677
678 // Night
679 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
680
681 // Build Dusk/Night ownship icons
682 double factor_dusk = 0.5;
683 double factor_night = 0.25;
684
685 // Red
686 m_os_image_red_day = style->GetIcon(_T("ship-red")).ConvertToImage();
687
688 int rimg_width = m_os_image_red_day.GetWidth();
689 int rimg_height = m_os_image_red_day.GetHeight();
690
691 m_os_image_red_dusk = m_os_image_red_day.Copy();
692 m_os_image_red_night = m_os_image_red_day.Copy();
693
694 for (int iy = 0; iy < rimg_height; iy++) {
695 for (int ix = 0; ix < rimg_width; ix++) {
696 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
697 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
698 m_os_image_red_day.GetGreen(ix, iy),
699 m_os_image_red_day.GetBlue(ix, iy));
700 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
701 hsv.value = hsv.value * factor_dusk;
702 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
703 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
704
705 hsv = wxImage::RGBtoHSV(rgb);
706 hsv.value = hsv.value * factor_night;
707 nrgb = wxImage::HSVtoRGB(hsv);
708 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
709 }
710 }
711 }
712
713 // Grey
714 m_os_image_grey_day =
715 style->GetIcon(_T("ship-red")).ConvertToImage().ConvertToGreyscale();
716
717 int gimg_width = m_os_image_grey_day.GetWidth();
718 int gimg_height = m_os_image_grey_day.GetHeight();
719
720 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
721 m_os_image_grey_night = m_os_image_grey_day.Copy();
722
723 for (int iy = 0; iy < gimg_height; iy++) {
724 for (int ix = 0; ix < gimg_width; ix++) {
725 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
726 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
727 m_os_image_grey_day.GetGreen(ix, iy),
728 m_os_image_grey_day.GetBlue(ix, iy));
729 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
730 hsv.value = hsv.value * factor_dusk;
731 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
732 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
733
734 hsv = wxImage::RGBtoHSV(rgb);
735 hsv.value = hsv.value * factor_night;
736 nrgb = wxImage::HSVtoRGB(hsv);
737 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
738 }
739 }
740 }
741
742 // Yellow
743 m_os_image_yellow_day = m_os_image_red_day.Copy();
744
745 gimg_width = m_os_image_yellow_day.GetWidth();
746 gimg_height = m_os_image_yellow_day.GetHeight();
747
748 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
749 m_os_image_yellow_night = m_os_image_red_day.Copy();
750
751 for (int iy = 0; iy < gimg_height; iy++) {
752 for (int ix = 0; ix < gimg_width; ix++) {
753 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
754 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
755 m_os_image_yellow_day.GetGreen(ix, iy),
756 m_os_image_yellow_day.GetBlue(ix, iy));
757 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
758 hsv.hue += 60. / 360.; // shift to yellow
759 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
760 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
761
762 hsv = wxImage::RGBtoHSV(rgb);
763 hsv.value = hsv.value * factor_dusk;
764 hsv.hue += 60. / 360.; // shift to yellow
765 nrgb = wxImage::HSVtoRGB(hsv);
766 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
767
768 hsv = wxImage::RGBtoHSV(rgb);
769 hsv.hue += 60. / 360.; // shift to yellow
770 hsv.value = hsv.value * factor_night;
771 nrgb = wxImage::HSVtoRGB(hsv);
772 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
773 }
774 }
775 }
776
777 // Set initial pointers to ownship images
778 m_pos_image_red = &m_os_image_red_day;
779 m_pos_image_yellow = &m_os_image_yellow_day;
780 m_pos_image_grey = &m_os_image_grey_day;
781
782 SetUserOwnship();
783
784 m_pBrightPopup = NULL;
785
786#ifdef ocpnUSE_GL
787 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
788#endif
789
790 int gridFontSize = 8;
791#if defined(__WXOSX__) || defined(__WXGTK3__)
792 // Support scaled HDPI displays.
793 gridFontSize *= GetContentScaleFactor();
794#endif
795
796 m_pgridFont = FontMgr::Get().FindOrCreateFont(
797 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
798 FALSE, wxString(_T ( "Arial" )));
799
800 m_Piano = new Piano(this);
801
802 m_bShowCompassWin = true;
803
804 m_Compass = new ocpnCompass(this);
805 m_Compass->SetScaleFactor(g_compass_scalefactor);
806 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
807
808 m_notification_button = new NotificationButton(this);
809 m_notification_button->SetScaleFactor(g_compass_scalefactor);
810 m_notification_button->Show(true);
811
812 m_pianoFrozen = false;
813
814 SetMinSize(wxSize(200, 200));
815
816 m_displayScale = 1.0;
817#if defined(__WXOSX__) || defined(__WXGTK3__)
818 // Support scaled HDPI displays.
819 m_displayScale = GetContentScaleFactor();
820#endif
821 VPoint.SetPixelScale(m_displayScale);
822
823#ifdef HAVE_WX_GESTURE_EVENTS
824 // if (!m_glcc)
825 {
826 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
827 wxLogError("Failed to enable touch events");
828 }
829
830 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
831
832 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
833 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
834
835 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
836 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
837
838 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
839 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
840
841 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
842 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
843 }
844#endif
845
846 // Listen for notification events
847 auto &noteman = NotificationManager::GetInstance();
848
849 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
850 evt_notificationlist_change_listener.Listen(
851 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
852 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
853 if (m_NotificationsList && m_NotificationsList->IsShown()) {
854 m_NotificationsList->ReloadNotificationList();
855 }
856 Refresh();
857 });
858}
859
860ChartCanvas::~ChartCanvas() {
861 delete pThumbDIBShow;
862
863 // Delete Cursors
864 delete pCursorLeft;
865 delete pCursorRight;
866 delete pCursorUp;
867 delete pCursorDown;
868 delete pCursorArrow;
869 delete pCursorPencil;
870 delete pCursorCross;
871
872 delete pPanTimer;
873 delete pMovementTimer;
874 delete pMovementStopTimer;
875 delete pCurTrackTimer;
876 delete pRotDefTimer;
877 delete m_DoubleClickTimer;
878
879 delete m_pTrackRolloverWin;
880 delete m_pRouteRolloverWin;
881 delete m_pAISRolloverWin;
882 delete m_pBrightPopup;
883
884 delete m_pCIWin;
885
886 delete pscratch_bm;
887
888 m_dc_route.SelectObject(wxNullBitmap);
889 delete proute_bm;
890
891 delete pWorldBackgroundChart;
892 delete pss_overlay_bmp;
893
894 delete m_pEM_Feet;
895 delete m_pEM_Meters;
896 delete m_pEM_Fathoms;
897
898 delete m_pEM_OverZoom;
899 // delete m_pEM_CM93Offset;
900
901 delete m_prot_bm;
902
903 delete m_pos_image_user_day;
904 delete m_pos_image_user_dusk;
905 delete m_pos_image_user_night;
906 delete m_pos_image_user_grey_day;
907 delete m_pos_image_user_grey_dusk;
908 delete m_pos_image_user_grey_night;
909 delete m_pos_image_user_yellow_day;
910 delete m_pos_image_user_yellow_dusk;
911 delete m_pos_image_user_yellow_night;
912
913 delete undo;
914#ifdef ocpnUSE_GL
915 if (!g_bdisable_opengl) {
916 delete m_glcc;
917
918#if wxCHECK_VERSION(2, 9, 0)
919 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
920#endif
921 }
922#endif
923
924 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
925 // wx tries to deliver events to this canvas during destroy.
926 MUIBar *muiBar = m_muiBar;
927 m_muiBar = 0;
928 delete muiBar;
929 delete m_pQuilt;
930 delete m_pCurrentStack;
931 delete m_Compass;
932 delete m_Piano;
933}
934
935void ChartCanvas::RebuildCursors() {
936 delete pCursorLeft;
937 delete pCursorRight;
938 delete pCursorUp;
939 delete pCursorDown;
940 delete pCursorArrow;
941 delete pCursorPencil;
942 delete pCursorCross;
943
944 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
945 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
946
947 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
948
949 wxImage ICursorLeft = style->GetIcon(_T("left")).ConvertToImage();
950 wxImage ICursorRight = style->GetIcon(_T("right")).ConvertToImage();
951 wxImage ICursorUp = style->GetIcon(_T("up")).ConvertToImage();
952 wxImage ICursorDown = style->GetIcon(_T("down")).ConvertToImage();
953 wxImage ICursorPencil =
954 style->GetIconScaled(_T("pencil"), pencilScale).ConvertToImage();
955 wxImage ICursorCross = style->GetIcon(_T("cross")).ConvertToImage();
956
957#if !defined(__WXMSW__) && !defined(__WXQT__)
958 ICursorLeft.ConvertAlphaToMask(128);
959 ICursorRight.ConvertAlphaToMask(128);
960 ICursorUp.ConvertAlphaToMask(128);
961 ICursorDown.ConvertAlphaToMask(128);
962 ICursorPencil.ConvertAlphaToMask(10);
963 ICursorCross.ConvertAlphaToMask(10);
964#endif
965
966 if (ICursorLeft.Ok()) {
967 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
968 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
969 pCursorLeft = new wxCursor(ICursorLeft);
970 } else
971 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
972
973 if (ICursorRight.Ok()) {
974 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
975 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
976 pCursorRight = new wxCursor(ICursorRight);
977 } else
978 pCursorRight = new wxCursor(wxCURSOR_ARROW);
979
980 if (ICursorUp.Ok()) {
981 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
982 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
983 pCursorUp = new wxCursor(ICursorUp);
984 } else
985 pCursorUp = new wxCursor(wxCURSOR_ARROW);
986
987 if (ICursorDown.Ok()) {
988 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
989 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
990 pCursorDown = new wxCursor(ICursorDown);
991 } else
992 pCursorDown = new wxCursor(wxCURSOR_ARROW);
993
994 if (ICursorPencil.Ok()) {
995 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
996 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
997 pCursorPencil = new wxCursor(ICursorPencil);
998 } else
999 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
1000
1001 if (ICursorCross.Ok()) {
1002 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
1003 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
1004 pCursorCross = new wxCursor(ICursorCross);
1005 } else
1006 pCursorCross = new wxCursor(wxCURSOR_ARROW);
1007
1008 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
1009 pPlugIn_Cursor = NULL;
1010}
1011
1012void ChartCanvas::CanvasApplyLocale() {
1013 CreateDepthUnitEmbossMaps(m_cs);
1014 CreateOZEmbossMapData(m_cs);
1015}
1016
1017void ChartCanvas::SetupGlCanvas() {
1018#ifndef __ANDROID__
1019#ifdef ocpnUSE_GL
1020 if (!g_bdisable_opengl) {
1021 if (g_bopengl) {
1022 wxLogMessage(_T("Creating glChartCanvas"));
1023 m_glcc = new glChartCanvas(this);
1024
1025 // We use one context for all GL windows, so that textures etc will be
1026 // automatically shared
1027 if (IsPrimaryCanvas()) {
1028 // qDebug() << "Creating Primary Context";
1029
1030 // wxGLContextAttrs ctxAttr;
1031 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
1032 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
1033 // NULL, &ctxAttr);
1034 wxGLContext *pctx = new wxGLContext(m_glcc);
1035 m_glcc->SetContext(pctx);
1036 g_pGLcontext = pctx; // Save a copy of the common context
1037 } else {
1038#ifdef __WXOSX__
1039 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
1040#else
1041 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
1042 // saved common context
1043#endif
1044 }
1045 }
1046 }
1047#endif
1048#endif
1049
1050#ifdef __ANDROID__ // ocpnUSE_GL
1051 if (!g_bdisable_opengl) {
1052 if (g_bopengl) {
1053 // qDebug() << "SetupGlCanvas";
1054 wxLogMessage(_T("Creating glChartCanvas"));
1055
1056 // We use one context for all GL windows, so that textures etc will be
1057 // automatically shared
1058 if (IsPrimaryCanvas()) {
1059 qDebug() << "Creating Primary glChartCanvas";
1060
1061 // wxGLContextAttrs ctxAttr;
1062 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
1063 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
1064 // NULL, &ctxAttr);
1065 m_glcc = new glChartCanvas(this);
1066
1067 wxGLContext *pctx = new wxGLContext(m_glcc);
1068 m_glcc->SetContext(pctx);
1069 g_pGLcontext = pctx; // Save a copy of the common context
1070 m_glcc->m_pParentCanvas = this;
1071 // m_glcc->Reparent(this);
1072 } else {
1073 qDebug() << "Creating Secondary glChartCanvas";
1074 // QGLContext *pctx =
1075 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
1076 // << "pctx: " << pctx;
1077
1078 m_glcc = new glChartCanvas(
1079 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
1080 // m_glcc = new glChartCanvas(this, pctx); //Shared
1081 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
1082 wxGLContext *pwxctx = new wxGLContext(m_glcc);
1083 m_glcc->SetContext(pwxctx);
1084 m_glcc->m_pParentCanvas = this;
1085 // m_glcc->Reparent(this);
1086 }
1087 }
1088 }
1089#endif
1090}
1091
1092void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
1093 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1094
1095 // On Android, we get a KillFocus on just about every keystroke.
1096 // Why?
1097#ifdef __ANDROID__
1098 return;
1099#endif
1100
1101 // Special logic:
1102 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
1103 // canvas focus. Why??? Who knows... So, we provide for this case by
1104 // starting a timer if required to actually Finish() a route on a legitimate
1105 // focus change, but not if the focus is quickly regained ( <20 msec.) on
1106 // this canvas.
1107#ifdef __WXOSX__
1108 if (m_routeState && m_FinishRouteOnKillFocus)
1109 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
1110#else
1111 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1112#endif
1113}
1114
1115void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
1116 m_routeFinishTimer.Stop();
1117
1118 // Try to keep the global top-line menubar selections up to date with the
1119 // current "focus" canvas
1120 gFrame->UpdateGlobalMenuItems(this);
1121
1122 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1123}
1124
1125void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
1126 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1127}
1128
1129#ifdef HAVE_WX_GESTURE_EVENTS
1130void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
1131 /* we defer the popup menu call upon the leftup event
1132 else the menu disappears immediately,
1133 (see
1134 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
1135 */
1136 m_popupWanted = true;
1137}
1138
1139void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
1140 // not implemented yet
1141}
1142
1143void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1144
1145void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1146
1147void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1148 wxPoint pos = event.GetPosition();
1149
1150 m_leftdown = false;
1151
1152 if (!m_popupWanted) {
1153 wxMouseEvent ev(wxEVT_LEFT_UP);
1154 ev.m_x = pos.x;
1155 ev.m_y = pos.y;
1156 MouseEvent(ev);
1157 return;
1158 }
1159
1160 m_popupWanted = false;
1161
1162 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1163 ev.m_x = pos.x;
1164 ev.m_y = pos.y;
1165
1166 MouseEvent(ev);
1167}
1168
1169void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1170 m_leftdown = true;
1171
1172 wxPoint pos = event.GetPosition();
1173 MouseEvent(event);
1174}
1175
1176void ChartCanvas::OnMotion(wxMouseEvent &event) {
1177 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1178 dragging, upon simple click, and without the OnLeftDown event before Thus,
1179 this consists in skiping it, and setting the leftdown bit according to a
1180 status that we trust */
1181 event.m_leftDown = m_leftdown;
1182 MouseEvent(event);
1183}
1184
1185void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1186 /* there are spurious end zoom events upon right-click */
1187 if (event.IsGestureEnd()) return;
1188
1189 double factor = event.GetZoomFactor();
1190
1191 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1192 m_oldVPSScale = GetVPScale();
1193 }
1194
1195 double current_vps = GetVPScale();
1196 double wanted_factor = m_oldVPSScale / current_vps * factor;
1197
1198 ZoomCanvas(wanted_factor, true, false);
1199
1200 // Allow combined zoom/pan operation
1201 if (event.IsGestureStart()) {
1202 m_zoomStartPoint = event.GetPosition();
1203 } else {
1204 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1205 PanCanvas(-delta.x, -delta.y);
1206 m_zoomStartPoint = event.GetPosition();
1207 }
1208}
1209
1210void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1211
1212void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1213 DoRotateCanvas(0.0);
1214}
1215#endif /* HAVE_WX_GESTURE_EVENTS */
1216
1217void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1218 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1219 m_vLat = pcc->iLat;
1220 m_vLon = pcc->iLon;
1221
1222 m_restore_dbindex = pcc->DBindex;
1223 m_bFollow = pcc->bFollow;
1224 if (pcc->GroupID < 0) pcc->GroupID = 0;
1225
1226 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1227 m_groupIndex = 0;
1228 else
1229 m_groupIndex = pcc->GroupID;
1230
1231 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1232
1233 ShowTides(pcc->bShowTides);
1234 ShowCurrents(pcc->bShowCurrents);
1235
1236 SetShowDepthUnits(pcc->bShowDepthUnits);
1237 SetShowGrid(pcc->bShowGrid);
1238 SetShowOutlines(pcc->bShowOutlines);
1239
1240 SetShowAIS(pcc->bShowAIS);
1241 SetAttenAIS(pcc->bAttenAIS);
1242
1243 // ENC options
1244 SetShowENCText(pcc->bShowENCText);
1245 m_encDisplayCategory = pcc->nENCDisplayCategory;
1246 m_encShowDepth = pcc->bShowENCDepths;
1247 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1248 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1249 m_encShowLights = pcc->bShowENCLights;
1250 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1251 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1252 m_encShowDataQual = pcc->bShowENCDataQuality;
1253
1254 bool courseUp = pcc->bCourseUp;
1255 bool headUp = pcc->bHeadUp;
1256 m_upMode = NORTH_UP_MODE;
1257 if (courseUp)
1258 m_upMode = COURSE_UP_MODE;
1259 else if (headUp)
1260 m_upMode = HEAD_UP_MODE;
1261
1262 m_bLookAhead = pcc->bLookahead;
1263
1264 m_singleChart = NULL;
1265}
1266
1267void ChartCanvas::ApplyGlobalSettings() {
1268 // GPS compas window
1269 if (m_Compass) {
1270 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1271 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1272 }
1273 m_notification_button->UpdateStatus();
1274}
1275
1276void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1277 bool groupOK = CheckGroup(m_groupIndex);
1278
1279 if (!groupOK) {
1280 SetGroupIndex(m_groupIndex, true);
1281 }
1282}
1283
1284void ChartCanvas::SetShowGPS(bool bshow) {
1285 if (m_bShowGPS != bshow) {
1286 delete m_Compass;
1287 m_Compass = new ocpnCompass(this, bshow);
1288 m_Compass->SetScaleFactor(g_compass_scalefactor);
1289 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1290 }
1291 m_bShowGPS = bshow;
1292}
1293
1294void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1295 m_bShowCompassWin = bshow;
1296 if (m_Compass) {
1297 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1298 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1299 }
1300}
1301
1302int ChartCanvas::GetPianoHeight() {
1303 int height = 0;
1304 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1305
1306 return height;
1307}
1308
1309void ChartCanvas::ConfigureChartBar() {
1310 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1311
1312 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
1313 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
1314
1315 if (GetQuiltMode()) {
1316 m_Piano->SetRoundedRectangles(true);
1317 }
1318 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
1319 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon(_T("polyprj"))));
1320 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
1321}
1322
1323void ChartCanvas::ShowTides(bool bShow) {
1324 gFrame->LoadHarmonics();
1325
1326 if (ptcmgr->IsReady()) {
1327 SetbShowTide(bShow);
1328
1329 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1330 } else {
1331 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1332 SetbShowTide(false);
1333 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1334 }
1335
1336 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1337 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1338
1339 // TODO
1340 // if( GetbShowTide() ) {
1341 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1342 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1343 // update
1344 // } else
1345 // FrameTCTimer.Stop();
1346}
1347
1348void ChartCanvas::ShowCurrents(bool bShow) {
1349 gFrame->LoadHarmonics();
1350
1351 if (ptcmgr->IsReady()) {
1352 SetbShowCurrent(bShow);
1353 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1354 } else {
1355 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1356 SetbShowCurrent(false);
1357 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1358 }
1359
1360 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1361 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1362
1363 // TODO
1364 // if( GetbShowCurrent() ) {
1365 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1366 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1367 // update
1368 // } else
1369 // FrameTCTimer.Stop();
1370}
1371
1372// TODO
1373extern bool g_bPreserveScaleOnX;
1374extern ChartDummy *pDummyChart;
1375extern int g_sticky_chart;
1376
1377void ChartCanvas::canvasRefreshGroupIndex(void) { SetGroupIndex(m_groupIndex); }
1378
1379void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1380 SetAlertString(_T(""));
1381
1382 int new_index = index;
1383 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1384
1385 bool bgroup_override = false;
1386 int old_group_index = new_index;
1387
1388 if (!CheckGroup(new_index)) {
1389 new_index = 0;
1390 bgroup_override = true;
1391 }
1392
1393 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1394 new_index = index;
1395
1396 // Get the currently displayed chart native scale, and the current ViewPort
1397 int current_chart_native_scale = GetCanvasChartNativeScale();
1398 ViewPort vp = GetVP();
1399
1400 m_groupIndex = new_index;
1401
1402 // Are there ENCs in this group
1403 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1404
1405 // Update the MUIBar for ENC availability
1406 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1407
1408 // Allow the chart database to pre-calculate the MBTile inclusion test
1409 // boolean...
1410 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1411
1412 // Invalidate the "sticky" chart on group change, since it might not be in
1413 // the new group
1414 g_sticky_chart = -1;
1415
1416 // We need a chartstack and quilt to figure out which chart to open in the
1417 // new group
1418 UpdateCanvasOnGroupChange();
1419
1420 int dbi_now = -1;
1421 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1422
1423 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1424
1425 // If a new reference chart is indicated, set a good scale for it.
1426 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1427 double best_scale = GetBestStartScale(dbi_hint, vp);
1428 SetVPScale(best_scale);
1429 }
1430
1431 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1432
1433 // Refresh the canvas, selecting the "best" chart,
1434 // applying the prior ViewPort exactly
1435 canvasChartsRefresh(dbi_hint);
1436
1437 UpdateCanvasControlBar();
1438
1439 if (!autoSwitch && bgroup_override) {
1440 // show a short timed message box
1441 wxString msg(_("Group \""));
1442
1443 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1444 msg += pGroup->m_group_name;
1445
1446 msg += _("\" is empty.");
1447
1448 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1449
1450 return;
1451 }
1452
1453 // Message box is deferred so that canvas refresh occurs properly before
1454 // dialog
1455 if (bgroup_override) {
1456 wxString msg(_("Group \""));
1457
1458 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1459 msg += pGroup->m_group_name;
1460
1461 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1462
1463 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1464 }
1465}
1466
1467bool ChartCanvas::CheckGroup(int igroup) {
1468 if (!ChartData) return true; // Not known yet...
1469
1470 if (igroup == 0) return true; // "all charts" is always OK
1471
1472 if (igroup < 0) // negative group is an error
1473 return false;
1474
1475 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1476
1477 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1478 // and auto-shift to group 0
1479 return false;
1480
1481 for (const auto &elem : pGroup->m_element_array) {
1482 for (unsigned int ic = 0;
1483 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1484 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1485 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1486
1487 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1488 }
1489 }
1490
1491 // If necessary, check for GSHHS
1492 for (const auto &elem : pGroup->m_element_array) {
1493 const wxString &element_root = elem.m_element_name;
1494 wxString test_string = _T("GSHH");
1495 if (element_root.Upper().Contains(test_string)) return true;
1496 }
1497
1498 return false;
1499}
1500
1501void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1502 if (!ChartData) return;
1503
1504 AbstractPlatform::ShowBusySpinner();
1505
1506 double old_scale = GetVPScale();
1507 InvalidateQuilt();
1508 SetQuiltRefChart(-1);
1509
1510 m_singleChart = NULL;
1511
1512 // delete m_pCurrentStack;
1513 // m_pCurrentStack = NULL;
1514
1515 // Build a new ChartStack
1516 if (!m_pCurrentStack) {
1517 m_pCurrentStack = new ChartStack;
1518 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1519 }
1520
1521 if (-1 != dbi_hint) {
1522 if (GetQuiltMode()) {
1523 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1524 SetQuiltRefChart(dbi_hint);
1525 } else {
1526 // Open the saved chart
1527 ChartBase *pTentative_Chart;
1528 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1529
1530 if (pTentative_Chart) {
1531 /* m_singleChart is always NULL here, (set above) should this go before
1532 * that? */
1533 if (m_singleChart) m_singleChart->Deactivate();
1534
1535 m_singleChart = pTentative_Chart;
1536 m_singleChart->Activate();
1537
1538 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1539 GetpCurrentStack(), m_singleChart->GetFullPath());
1540 }
1541 }
1542
1543 // refresh_Piano();
1544 } else {
1545 // Select reference chart from the stack, as though clicked by user
1546 // Make it the smallest scale chart on the stack
1547 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1548 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1549 SetQuiltRefChart(selected_index);
1550 }
1551
1552 // Validate the correct single chart, or set the quilt mode as appropriate
1553 SetupCanvasQuiltMode();
1554 if (!GetQuiltMode() && m_singleChart == 0) {
1555 // use a dummy like in DoChartUpdate
1556 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1557 m_singleChart = pDummyChart;
1558 SetVPScale(old_scale);
1559 }
1560
1561 ReloadVP();
1562
1563 UpdateCanvasControlBar();
1564 UpdateGPSCompassStatusBox(true);
1565
1566 SetCursor(wxCURSOR_ARROW);
1567
1568 AbstractPlatform::HideBusySpinner();
1569}
1570
1571bool ChartCanvas::DoCanvasUpdate(void) {
1572 double tLat, tLon; // Chart Stack location
1573 double vpLat, vpLon; // ViewPort location
1574 bool blong_jump = false;
1575 meters_to_shift = 0;
1576 dir_to_shift = 0;
1577
1578 bool bNewChart = false;
1579 bool bNewView = false;
1580 bool bCanvasChartAutoOpen = true; // debugging
1581
1582 bool bNewPiano = false;
1583 bool bOpenSpecified;
1584 ChartStack LastStack;
1585 ChartBase *pLast_Ch;
1586
1587 ChartStack WorkStack;
1588
1589 if (bDBUpdateInProgress) return false;
1590 if (!ChartData) return false;
1591
1592 if (ChartData->IsBusy()) return false;
1593
1594 // Startup case:
1595 // Quilting is enabled, but the last chart seen was not quiltable
1596 // In this case, drop to single chart mode, set persistence flag,
1597 // And open the specified chart
1598 // TODO implement this
1599 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1600 // if( GetQuiltMode() ) {
1601 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1602 // gFrame->ToggleQuiltMode();
1603 // m_bpersistent_quilt = true;
1604 // m_singleChart = NULL;
1605 // }
1606 // }
1607 // }
1608
1609 // If in auto-follow mode, use the current glat,glon to build chart
1610 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1611 // other means
1612
1613 if (m_bFollow) {
1614 tLat = gLat;
1615 tLon = gLon;
1616
1617 // Set the ViewPort center based on the OWNSHIP offset
1618 double dx = m_OSoffsetx;
1619 double dy = m_OSoffsety;
1620 double d_east = dx / GetVP().view_scale_ppm;
1621 double d_north = dy / GetVP().view_scale_ppm;
1622
1623 if (GetUpMode() == NORTH_UP_MODE) {
1624 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1625 } else {
1626 double offset_angle = atan2(d_north, d_east);
1627 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1628 double chart_angle = GetVPRotation();
1629 double target_angle = chart_angle + offset_angle;
1630 double d_east_mod = offset_distance * cos(target_angle);
1631 double d_north_mod = offset_distance * sin(target_angle);
1632 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1633 }
1634
1635 extern double gCog_gt;
1636
1637 // on lookahead mode, adjust the vp center point
1638 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1639 double cog_to_use = gCog;
1640 if (g_btenhertz &&
1641 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1642 cog_to_use = gCog_gt;
1643 blong_jump = true;
1644 }
1645 if (!g_btenhertz) cog_to_use = g_COGAvg;
1646
1647 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1648
1649 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1650 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1651
1652 double pixel_delta_tent =
1653 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1654
1655 double pixel_delta = 0;
1656
1657 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1658 // avoid jumping of the vp center point during slow maneuvering, or at
1659 // anchor....
1660 if (!std::isnan(gSog)) {
1661 if (gSog < 2.0)
1662 pixel_delta = 0.;
1663 else
1664 pixel_delta = pixel_delta_tent;
1665 }
1666
1667 meters_to_shift = 0;
1668 dir_to_shift = 0;
1669 if (!std::isnan(gCog)) {
1670 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1671 dir_to_shift = cog_to_use;
1672 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1673 &vpLon);
1674 } else {
1675 vpLat = gLat;
1676 vpLon = gLon;
1677 }
1678 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1679 m_OSoffsetx = 0; // center ownship on loss of GPS
1680 m_OSoffsety = 0;
1681 vpLat = gLat;
1682 vpLon = gLon;
1683 }
1684
1685 } else {
1686 tLat = m_vLat;
1687 tLon = m_vLon;
1688 vpLat = m_vLat;
1689 vpLon = m_vLon;
1690 }
1691
1692 if (GetQuiltMode()) {
1693 int current_db_index = -1;
1694 if (m_pCurrentStack)
1695 current_db_index =
1696 m_pCurrentStack
1697 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1698 // chart dbIndex
1699 else
1700 m_pCurrentStack = new ChartStack;
1701
1702 // This logic added to enable opening a chart when there is no
1703 // previous chart indication, either from inital startup, or from adding
1704 // new chart directory
1705 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1706 m_pCurrentStack) {
1707 if (m_pCurrentStack->nEntry) {
1708 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1709 1); // smallest scale
1710 SelectQuiltRefdbChart(new_dbIndex, true);
1711 m_bautofind = false;
1712 }
1713 }
1714
1715 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1716 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1717
1718 if (m_bFirstAuto) {
1719 // Allow the chart database to pre-calculate the MBTile inclusion test
1720 // boolean...
1721 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1722
1723 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1724 // physical pixels. On standard DPI displays where logical = physical
1725 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1726 // logical pixels, this ratio would be 0.5.
1727 double proposed_scale_onscreen =
1728 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1729
1730 int initial_db_index = m_restore_dbindex;
1731 if (initial_db_index < 0) {
1732 if (m_pCurrentStack->nEntry) {
1733 initial_db_index =
1734 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1735 } else
1736 m_bautofind = true; // initial_db_index = 0;
1737 }
1738
1739 if (m_pCurrentStack->nEntry) {
1740 int initial_type = ChartData->GetDBChartType(initial_db_index);
1741
1742 // Check to see if the target new chart is quiltable as a reference
1743 // chart
1744
1745 if (!IsChartQuiltableRef(initial_db_index)) {
1746 // If it is not quiltable, then walk the stack up looking for a
1747 // satisfactory chart i.e. one that is quiltable and of the same type
1748 // XXX if there's none?
1749 int stack_index = 0;
1750
1751 if (stack_index >= 0) {
1752 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1753 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1754 if (IsChartQuiltableRef(test_db_index) &&
1755 (initial_type ==
1756 ChartData->GetDBChartType(initial_db_index))) {
1757 initial_db_index = test_db_index;
1758 break;
1759 }
1760 stack_index++;
1761 }
1762 }
1763 }
1764
1765 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1766 if (pc) {
1767 SetQuiltRefChart(initial_db_index);
1768 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1769 }
1770 }
1771 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1772 // just GetVPScale(), so I'm not sure why it's necessary to define the
1773 // proposed_scale_onscreen variable.
1774 bNewView |= SetViewPoint(vpLat, vpLon,
1775 GetCanvasScaleFactor() / proposed_scale_onscreen,
1776 0, GetVPRotation());
1777 }
1778 // Measure rough jump distance if in bfollow mode
1779 // No good reason to do smooth pan for
1780 // jump distance more than one screen width at scale.
1781 bool super_jump = false;
1782 if (m_bFollow) {
1783 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1784 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1785 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1786 }
1787 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead) {
1788 int nstep = 5;
1789 if (blong_jump) nstep = 20;
1790 StartTimedMovementVP(vpLat, vpLon, nstep);
1791 } else {
1792 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1793 }
1794
1795 goto update_finish;
1796 }
1797
1798 // Single Chart Mode from here....
1799 pLast_Ch = m_singleChart;
1800 ChartTypeEnum new_open_type;
1801 ChartFamilyEnum new_open_family;
1802 if (pLast_Ch) {
1803 new_open_type = pLast_Ch->GetChartType();
1804 new_open_family = pLast_Ch->GetChartFamily();
1805 } else {
1806 new_open_type = CHART_TYPE_KAP;
1807 new_open_family = CHART_FAMILY_RASTER;
1808 }
1809
1810 bOpenSpecified = m_bFirstAuto;
1811
1812 // Make sure the target stack is valid
1813 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1814
1815 // Build a chart stack based on tLat, tLon
1816 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1817 m_groupIndex)) { // Bogus Lat, Lon?
1818 if (NULL == pDummyChart) {
1819 pDummyChart = new ChartDummy;
1820 bNewChart = true;
1821 }
1822
1823 if (m_singleChart)
1824 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1825
1826 m_singleChart = pDummyChart;
1827
1828 // If the current viewpoint is invalid, set the default scale to
1829 // something reasonable.
1830 double set_scale = GetVPScale();
1831 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1832
1833 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1834
1835 // If the chart stack has just changed, there is new status
1836 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1837 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1838 bNewPiano = true;
1839 bNewChart = true;
1840 }
1841 }
1842
1843 // Copy the new (by definition empty) stack into the target stack
1844 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1845
1846 goto update_finish;
1847 }
1848
1849 // Check to see if Chart Stack has changed
1850 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1851 // New chart stack, so...
1852 bNewPiano = true;
1853
1854 // Save a copy of the current stack
1855 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1856
1857 // Copy the new stack into the target stack
1858 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1859
1860 // Is Current Chart in new stack?
1861
1862 int tEntry = -1;
1863 if (NULL != m_singleChart) // this handles startup case
1864 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1865 m_singleChart->GetFullPath());
1866
1867 if (tEntry != -1) { // m_singleChart is in the new stack
1868 m_pCurrentStack->CurrentStackEntry = tEntry;
1869 bNewChart = false;
1870 }
1871
1872 else // m_singleChart is NOT in new stack
1873 { // So, need to open a new chart
1874 // Find the largest scale raster chart that opens OK
1875
1876 ChartBase *pProposed = NULL;
1877
1878 if (bCanvasChartAutoOpen) {
1879 bool search_direction =
1880 false; // default is to search from lowest to highest
1881 int start_index = 0;
1882
1883 // A special case: If panning at high scale, open largest scale
1884 // chart first
1885 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1886 (LastStack.nEntry == 0)) {
1887 search_direction = true;
1888 start_index = m_pCurrentStack->nEntry - 1;
1889 }
1890
1891 // Another special case, open specified index on program start
1892 if (bOpenSpecified) {
1893 search_direction = false;
1894 start_index = 0;
1895 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1896 start_index = 0;
1897
1898 new_open_type = CHART_TYPE_DONTCARE;
1899 }
1900
1901 pProposed = ChartData->OpenStackChartConditional(
1902 m_pCurrentStack, start_index, search_direction, new_open_type,
1903 new_open_family);
1904
1905 // Try to open other types/families of chart in some priority
1906 if (NULL == pProposed)
1907 pProposed = ChartData->OpenStackChartConditional(
1908 m_pCurrentStack, start_index, search_direction,
1909 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1910
1911 if (NULL == pProposed)
1912 pProposed = ChartData->OpenStackChartConditional(
1913 m_pCurrentStack, start_index, search_direction,
1914 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1915
1916 bNewChart = true;
1917
1918 } // bCanvasChartAutoOpen
1919
1920 else
1921 pProposed = NULL;
1922
1923 // If no go, then
1924 // Open a Dummy Chart
1925 if (NULL == pProposed) {
1926 if (NULL == pDummyChart) {
1927 pDummyChart = new ChartDummy;
1928 bNewChart = true;
1929 }
1930
1931 if (pLast_Ch)
1932 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1933
1934 pProposed = pDummyChart;
1935 }
1936
1937 // Arriving here, pProposed points to an opened chart, or NULL.
1938 if (m_singleChart) m_singleChart->Deactivate();
1939 m_singleChart = pProposed;
1940
1941 if (m_singleChart) {
1942 m_singleChart->Activate();
1943 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1944 m_pCurrentStack, m_singleChart->GetFullPath());
1945 }
1946 } // need new chart
1947
1948 // Arriving here, m_singleChart is opened and OK, or NULL
1949 if (NULL != m_singleChart) {
1950 // Setup the view using the current scale
1951 double set_scale = GetVPScale();
1952
1953 // If the current viewpoint is invalid, set the default scale to
1954 // something reasonable.
1955 if (!GetVP().IsValid())
1956 set_scale = 1. / 20000.;
1957 else { // otherwise, match scale if elected.
1958 double proposed_scale_onscreen;
1959
1960 if (m_bFollow) { // autoset the scale only if in autofollow
1961 double new_scale_ppm =
1962 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1963 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1964 } else
1965 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1966
1967 // This logic will bring a new chart onscreen at roughly twice the true
1968 // paper scale equivalent. Note that first chart opened on application
1969 // startup (bOpenSpecified = true) will open at the config saved scale
1970 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1971 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1972 double equivalent_vp_scale =
1973 GetCanvasScaleFactor() / proposed_scale_onscreen;
1974 double new_scale_ppm =
1975 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1976 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1977 }
1978
1979 if (m_bFollow) { // bounds-check the scale only if in autofollow
1980 proposed_scale_onscreen =
1981 wxMin(proposed_scale_onscreen,
1982 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1983 GetCanvasWidth()));
1984 proposed_scale_onscreen =
1985 wxMax(proposed_scale_onscreen,
1986 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1987 g_b_overzoom_x));
1988 }
1989
1990 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1991 }
1992
1993 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1994 m_singleChart->GetChartSkew() * PI / 180.,
1995 GetVPRotation());
1996 }
1997 } // new stack
1998
1999 else // No change in Chart Stack
2000 {
2001 if ((m_bFollow) && m_singleChart)
2002 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
2003 m_singleChart->GetChartSkew() * PI / 180.,
2004 GetVPRotation());
2005 }
2006
2007update_finish:
2008
2009 // TODO
2010 // if( bNewPiano ) UpdateControlBar();
2011
2012 m_bFirstAuto = false; // Auto open on program start
2013
2014 // If we need a Refresh(), do it here...
2015 // But don't duplicate a Refresh() done by SetViewPoint()
2016 if (bNewChart && !bNewView) Refresh(false);
2017
2018#ifdef ocpnUSE_GL
2019 // If a new chart, need to invalidate gl viewport for refresh
2020 // so the fbo gets flushed
2021 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
2022#endif
2023
2024 return bNewChart | bNewView;
2025}
2026
2027void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
2028 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
2029
2030 SetQuiltRefChart(db_index);
2031 if (ChartData) {
2032 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
2033 if (pc) {
2034 if (b_autoscale) {
2035 double best_scale_ppm = GetBestVPScale(pc);
2036 SetVPScale(best_scale_ppm);
2037 }
2038 } else
2039 SetQuiltRefChart(-1);
2040 } else
2041 SetQuiltRefChart(-1);
2042}
2043
2044void ChartCanvas::SelectQuiltRefChart(int selected_index) {
2045 std::vector<int> piano_chart_index_array =
2046 GetQuiltExtendedStackdbIndexArray();
2047 int current_db_index = piano_chart_index_array[selected_index];
2048
2049 SelectQuiltRefdbChart(current_db_index);
2050}
2051
2052double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
2053 if (pchart) {
2054 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
2055
2056 if ((g_bPreserveScaleOnX) ||
2057 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2058 double new_scale_ppm = GetVPScale();
2059 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2060 } else {
2061 // This logic will bring the new chart onscreen at roughly twice the true
2062 // paper scale equivalent.
2063 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2064 double equivalent_vp_scale =
2065 GetCanvasScaleFactor() / proposed_scale_onscreen;
2066 double new_scale_ppm =
2067 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2068 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2069 }
2070
2071 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2072 // set. Otherwise, we get severe performance problems on all platforms
2073
2074 double max_underzoom_multiplier = 2.0;
2075 if (GetVP().b_quilt) {
2076 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2077 pchart->GetChartType(),
2078 pchart->GetChartFamily());
2079 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2080 }
2081
2082 proposed_scale_onscreen = wxMin(
2083 proposed_scale_onscreen,
2084 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2085 max_underzoom_multiplier);
2086
2087 // And, do not allow excessive overzoom either
2088 proposed_scale_onscreen =
2089 wxMax(proposed_scale_onscreen,
2090 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2091
2092 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2093 } else
2094 return 1.0;
2095}
2096
2097void ChartCanvas::SetupCanvasQuiltMode(void) {
2098 if (GetQuiltMode()) // going to quilt mode
2099 {
2100 ChartData->LockCache();
2101
2102 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2103
2104 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2105
2106 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2107 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2108 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2109 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2110
2111 m_Piano->SetRoundedRectangles(true);
2112
2113 // Select the proper Ref chart
2114 int target_new_dbindex = -1;
2115 if (m_pCurrentStack) {
2116 target_new_dbindex =
2117 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2118
2119 if (-1 != target_new_dbindex) {
2120 if (!IsChartQuiltableRef(target_new_dbindex)) {
2121 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2122 int type = ChartData->GetDBChartType(target_new_dbindex);
2123
2124 // walk the stack up looking for a satisfactory chart
2125 int stack_index = m_pCurrentStack->CurrentStackEntry;
2126
2127 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2128 (stack_index >= 0)) {
2129 int proj_tent = ChartData->GetDBChartProj(
2130 m_pCurrentStack->GetDBIndex(stack_index));
2131 int type_tent = ChartData->GetDBChartType(
2132 m_pCurrentStack->GetDBIndex(stack_index));
2133
2134 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2135 if ((proj == proj_tent) && (type_tent == type)) {
2136 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2137 break;
2138 }
2139 }
2140 stack_index++;
2141 }
2142 }
2143 }
2144 }
2145
2146 if (IsChartQuiltableRef(target_new_dbindex))
2147 SelectQuiltRefdbChart(target_new_dbindex,
2148 false); // Try not to allow a scale change
2149 else
2150 SelectQuiltRefdbChart(-1, false);
2151
2152 m_singleChart = NULL; // Bye....
2153
2154 // Re-qualify the quilt reference chart selection
2155 AdjustQuiltRefChart();
2156
2157 // Restore projection type saved on last quilt mode toggle
2158 // TODO
2159 // if(g_sticky_projection != -1)
2160 // GetVP().SetProjectionType(g_sticky_projection);
2161 // else
2162 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2163 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2164
2165 } else // going to SC Mode
2166 {
2167 std::vector<int> empty_array;
2168 m_Piano->SetActiveKeyArray(empty_array);
2169 m_Piano->SetNoshowIndexArray(empty_array);
2170 m_Piano->SetEclipsedIndexArray(empty_array);
2171
2172 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2173 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2174 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2175 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2176 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2177
2178 m_Piano->SetRoundedRectangles(false);
2179 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2180 }
2181
2182 // When shifting from quilt to single chart mode, select the "best" single
2183 // chart to show
2184 if (!GetQuiltMode()) {
2185 if (ChartData && ChartData->IsValid()) {
2186 UnlockQuilt();
2187
2188 double tLat, tLon;
2189 if (m_bFollow == true) {
2190 tLat = gLat;
2191 tLon = gLon;
2192 } else {
2193 tLat = m_vLat;
2194 tLon = m_vLon;
2195 }
2196
2197 if (!m_singleChart) {
2198 // Build a temporary chart stack based on tLat, tLon
2199 ChartStack TempStack;
2200 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2201 m_groupIndex);
2202
2203 // Iterate over the quilt charts actually shown, looking for the
2204 // largest scale chart that will be in the new chartstack.... This
2205 // will (almost?) always be the reference chart....
2206
2207 ChartBase *Candidate_Chart = NULL;
2208 int cur_max_scale = (int)1e8;
2209
2210 ChartBase *pChart = GetFirstQuiltChart();
2211 while (pChart) {
2212 // Is this pChart in new stack?
2213 int tEntry =
2214 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2215 if (tEntry != -1) {
2216 if (pChart->GetNativeScale() < cur_max_scale) {
2217 Candidate_Chart = pChart;
2218 cur_max_scale = pChart->GetNativeScale();
2219 }
2220 }
2221 pChart = GetNextQuiltChart();
2222 }
2223
2224 m_singleChart = Candidate_Chart;
2225
2226 // If the quilt is empty, there is no "best" chart.
2227 // So, open the smallest scale chart in the current stack
2228 if (NULL == m_singleChart) {
2229 m_singleChart = ChartData->OpenStackChartConditional(
2230 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2231 CHART_FAMILY_DONTCARE);
2232 }
2233 }
2234
2235 // Invalidate all the charts in the quilt,
2236 // as any cached data may be region based and not have fullscreen coverage
2237 InvalidateAllQuiltPatchs();
2238
2239 if (m_singleChart) {
2240 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2241 std::vector<int> one_array;
2242 one_array.push_back(dbi);
2243 m_Piano->SetActiveKeyArray(one_array);
2244 }
2245
2246 if (m_singleChart) {
2247 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2248 }
2249 }
2250 // Invalidate the current stack so that it will be rebuilt on next tick
2251 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2252 }
2253}
2254
2255bool ChartCanvas::IsTempMenuBarEnabled() {
2256#ifdef __WXMSW__
2257 int major;
2258 wxGetOsVersion(&major);
2259 return (major >
2260 5); // For Windows, function is only available on Vista and above
2261#else
2262 return true;
2263#endif
2264}
2265
2266double ChartCanvas::GetCanvasRangeMeters() {
2267 int width, height;
2268 GetSize(&width, &height);
2269 int minDimension = wxMin(width, height);
2270
2271 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2272 range *= cos(GetVP().clat * PI / 180.);
2273 return range;
2274}
2275
2276void ChartCanvas::SetCanvasRangeMeters(double range) {
2277 int width, height;
2278 GetSize(&width, &height);
2279 int minDimension = wxMin(width, height);
2280
2281 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2282 SetVPScale(scale_ppm / 2);
2283}
2284
2285bool ChartCanvas::SetUserOwnship() {
2286 // Look for user defined ownship image
2287 // This may be found in the shared data location along with other user
2288 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2289 if (pWayPointMan && pWayPointMan->DoesIconExist(_T("ownship"))) {
2290 double factor_dusk = 0.5;
2291 double factor_night = 0.25;
2292
2293 wxBitmap *pbmp = pWayPointMan->GetIconBitmap(_T("ownship"));
2294 m_pos_image_user_day = new wxImage;
2295 *m_pos_image_user_day = pbmp->ConvertToImage();
2296 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2297
2298 int gimg_width = m_pos_image_user_day->GetWidth();
2299 int gimg_height = m_pos_image_user_day->GetHeight();
2300
2301 // Make dusk and night images
2302 m_pos_image_user_dusk = new wxImage;
2303 m_pos_image_user_night = new wxImage;
2304
2305 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2306 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2307
2308 for (int iy = 0; iy < gimg_height; iy++) {
2309 for (int ix = 0; ix < gimg_width; ix++) {
2310 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2311 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2312 m_pos_image_user_day->GetGreen(ix, iy),
2313 m_pos_image_user_day->GetBlue(ix, iy));
2314 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2315 hsv.value = hsv.value * factor_dusk;
2316 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2317 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2318 nrgb.blue);
2319
2320 hsv = wxImage::RGBtoHSV(rgb);
2321 hsv.value = hsv.value * factor_night;
2322 nrgb = wxImage::HSVtoRGB(hsv);
2323 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2324 nrgb.blue);
2325 }
2326 }
2327 }
2328
2329 // Make some alternate greyed out day/dusk/night images
2330 m_pos_image_user_grey_day = new wxImage;
2331 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2332
2333 m_pos_image_user_grey_dusk = new wxImage;
2334 m_pos_image_user_grey_night = new wxImage;
2335
2336 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2337 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2338
2339 for (int iy = 0; iy < gimg_height; iy++) {
2340 for (int ix = 0; ix < gimg_width; ix++) {
2341 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2342 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2343 m_pos_image_user_grey_day->GetGreen(ix, iy),
2344 m_pos_image_user_grey_day->GetBlue(ix, iy));
2345 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2346 hsv.value = hsv.value * factor_dusk;
2347 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2348 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2349 nrgb.blue);
2350
2351 hsv = wxImage::RGBtoHSV(rgb);
2352 hsv.value = hsv.value * factor_night;
2353 nrgb = wxImage::HSVtoRGB(hsv);
2354 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2355 nrgb.blue);
2356 }
2357 }
2358 }
2359
2360 // Make a yellow image for rendering under low accuracy chart conditions
2361 m_pos_image_user_yellow_day = new wxImage;
2362 m_pos_image_user_yellow_dusk = new wxImage;
2363 m_pos_image_user_yellow_night = new wxImage;
2364
2365 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2366 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2367 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2368
2369 for (int iy = 0; iy < gimg_height; iy++) {
2370 for (int ix = 0; ix < gimg_width; ix++) {
2371 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2372 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2373 m_pos_image_user_grey_day->GetGreen(ix, iy),
2374 m_pos_image_user_grey_day->GetBlue(ix, iy));
2375
2376 // Simply remove all "blue" from the greyscaled image...
2377 // so, what is not black becomes yellow.
2378 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2379 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2380 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2381
2382 hsv = wxImage::RGBtoHSV(rgb);
2383 hsv.value = hsv.value * factor_dusk;
2384 nrgb = wxImage::HSVtoRGB(hsv);
2385 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2386
2387 hsv = wxImage::RGBtoHSV(rgb);
2388 hsv.value = hsv.value * factor_night;
2389 nrgb = wxImage::HSVtoRGB(hsv);
2390 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2391 0);
2392 }
2393 }
2394 }
2395
2396 return true;
2397 } else
2398 return false;
2399}
2400
2402 m_display_size_mm = size;
2403
2404 // int sx, sy;
2405 // wxDisplaySize( &sx, &sy );
2406
2407 // Calculate logical pixels per mm for later reference.
2408 wxSize sd = g_Platform->getDisplaySize();
2409 double horizontal = sd.x;
2410 // Set DPI (Win) scale factor
2411 g_scaler = g_Platform->GetDisplayDIPMult(this);
2412
2413 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2414 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2415
2416 if (ps52plib) {
2417 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2418 ps52plib->SetPPMM(m_pix_per_mm);
2419 }
2420
2421 wxString msg;
2422 msg.Printf(
2423 _T("Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): ")
2424 _T("%d:%d "),
2425 m_display_size_mm, sd.x, sd.y);
2426 wxLogMessage(msg);
2427
2428 int ssx, ssy;
2429 ssx = g_monitor_info[g_current_monitor].width;
2430 ssy = g_monitor_info[g_current_monitor].height;
2431 msg.Printf(_T("monitor size: %d %d"), ssx, ssy);
2432 wxLogMessage(msg);
2433
2434 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2435}
2436#if 0
2437void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2438{
2439 wxString msg(event.m_string.c_str(), wxConvUTF8);
2440 // if cpus are removed between runs
2441 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2442 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2443 }
2444
2445 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2446 {
2447 compress_msg_array.RemoveAt(event.thread);
2448 compress_msg_array.Insert( msg, event.thread);
2449 }
2450 else
2451 compress_msg_array.Add(msg);
2452
2453
2454 wxString combined_msg;
2455 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2456 combined_msg += compress_msg_array[i];
2457 combined_msg += _T("\n");
2458 }
2459
2460 bool skip = false;
2461 pprog->Update(pprog_count, combined_msg, &skip );
2462 pprog->SetSize(pprog_size);
2463 if(skip)
2464 b_skipout = skip;
2465}
2466#endif
2467void ChartCanvas::InvalidateGL() {
2468 if (!m_glcc) return;
2469#ifdef ocpnUSE_GL
2470 if (g_bopengl) m_glcc->Invalidate();
2471#endif
2472 if (m_Compass) m_Compass->UpdateStatus(true);
2473}
2474
2475int ChartCanvas::GetCanvasChartNativeScale() {
2476 int ret = 1;
2477 if (!VPoint.b_quilt) {
2478 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2479 } else
2480 ret = (int)m_pQuilt->GetRefNativeScale();
2481
2482 return ret;
2483}
2484
2485ChartBase *ChartCanvas::GetChartAtCursor() {
2486 ChartBase *target_chart;
2487 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2488 target_chart = m_singleChart;
2489 else if (VPoint.b_quilt)
2490 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2491 else
2492 target_chart = NULL;
2493 return target_chart;
2494}
2495
2496ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2497 ChartBase *target_chart;
2498 if (VPoint.b_quilt)
2499 target_chart =
2500 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2501 else
2502 target_chart = NULL;
2503 return target_chart;
2504}
2505
2506int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2507 int new_dbIndex = -1;
2508 if (!VPoint.b_quilt) {
2509 if (m_pCurrentStack) {
2510 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2511 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2512 if (sc >= scale) {
2513 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2514 break;
2515 }
2516 }
2517 }
2518 } else {
2519 // Using the current quilt, select a useable reference chart
2520 // Said chart will be in the extended (possibly full-screen) stack,
2521 // And will have a scale equal to or just greater than the stipulated
2522 // value
2523 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2524 if (im > 0) {
2525 for (unsigned int is = 0; is < im; is++) {
2526 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2527 m_pQuilt->GetExtendedStackIndexArray()[is]);
2528 if ((m.Scale_ge(
2529 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2530 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2531 break;
2532 }
2533 }
2534 }
2535 }
2536
2537 return new_dbIndex;
2538}
2539
2540void ChartCanvas::EnablePaint(bool b_enable) {
2541 m_b_paint_enable = b_enable;
2542#ifdef ocpnUSE_GL
2543 if (m_glcc) m_glcc->EnablePaint(b_enable);
2544#endif
2545}
2546
2547bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2548
2549void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2550
2551std::vector<int> ChartCanvas::GetQuiltIndexArray(void) {
2552 return m_pQuilt->GetQuiltIndexArray();
2553 ;
2554}
2555
2556void ChartCanvas::SetQuiltMode(bool b_quilt) {
2557 VPoint.b_quilt = b_quilt;
2558 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2559}
2560
2561bool ChartCanvas::GetQuiltMode(void) { return VPoint.b_quilt; }
2562
2563int ChartCanvas::GetQuiltReferenceChartIndex(void) {
2564 return m_pQuilt->GetRefChartdbIndex();
2565}
2566
2567void ChartCanvas::InvalidateAllQuiltPatchs(void) {
2568 m_pQuilt->InvalidateAllQuiltPatchs();
2569}
2570
2571ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2572 return m_pQuilt->GetLargestScaleChart();
2573}
2574
2575ChartBase *ChartCanvas::GetFirstQuiltChart() {
2576 return m_pQuilt->GetFirstChart();
2577}
2578
2579ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2580
2581int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2582
2583void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2584 m_pQuilt->SetHiliteIndex(dbIndex);
2585}
2586
2587void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2588 m_pQuilt->SetHiliteIndexArray(hilite_array);
2589}
2590
2591void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2592 m_pQuilt->ClearHiliteIndexArray();
2593}
2594
2595std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2596 bool flag2) {
2597 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2598}
2599
2600int ChartCanvas::GetQuiltRefChartdbIndex(void) {
2601 return m_pQuilt->GetRefChartdbIndex();
2602}
2603
2604std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2605 return m_pQuilt->GetExtendedStackIndexArray();
2606}
2607
2608std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2609 return m_pQuilt->GetFullscreenIndexArray();
2610}
2611
2612std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2613 return m_pQuilt->GetEclipsedStackIndexArray();
2614}
2615
2616void ChartCanvas::InvalidateQuilt(void) { return m_pQuilt->Invalidate(); }
2617
2618double ChartCanvas::GetQuiltMaxErrorFactor() {
2619 return m_pQuilt->GetMaxErrorFactor();
2620}
2621
2622bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2623 return m_pQuilt->IsChartQuiltableRef(db_index);
2624}
2625
2626bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2627 double chartMaxScale =
2628 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2629 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2630}
2631
2632void ChartCanvas::StartMeasureRoute() {
2633 if (!m_routeState) { // no measure tool if currently creating route
2634 if (m_bMeasure_Active) {
2635 g_pRouteMan->DeleteRoute(m_pMeasureRoute,
2636 NavObjectChanges::getInstance());
2637 m_pMeasureRoute = NULL;
2638 }
2639
2640 m_bMeasure_Active = true;
2641 m_nMeasureState = 1;
2642 m_bDrawingRoute = false;
2643
2644 SetCursor(*pCursorPencil);
2645 Refresh();
2646 }
2647}
2648
2649void ChartCanvas::CancelMeasureRoute() {
2650 m_bMeasure_Active = false;
2651 m_nMeasureState = 0;
2652 m_bDrawingRoute = false;
2653
2654 g_pRouteMan->DeleteRoute(m_pMeasureRoute, NavObjectChanges::getInstance());
2655 m_pMeasureRoute = NULL;
2656
2657 SetCursor(*pCursorArrow);
2658}
2659
2660ViewPort &ChartCanvas::GetVP() { return VPoint; }
2661
2662void ChartCanvas::SetVP(ViewPort &vp) {
2663 VPoint = vp;
2664 VPoint.SetPixelScale(m_displayScale);
2665}
2666
2667// void ChartCanvas::SetFocus()
2668// {
2669// printf("set %d\n", m_canvasIndex);
2670// //wxWindow:SetFocus();
2671// }
2672
2673void ChartCanvas::TriggerDeferredFocus() {
2674 // #if defined(__WXGTK__) || defined(__WXOSX__)
2675
2676 m_deferredFocusTimer.Start(20, true);
2677
2678#if defined(__WXGTK__) || defined(__WXOSX__)
2679 gFrame->Raise();
2680#endif
2681
2682 // gFrame->Raise();
2683 // #else
2684 // SetFocus();
2685 // Refresh(true);
2686 // #endif
2687}
2688
2689void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2690 SetFocus();
2691 Refresh(true);
2692}
2693
2694void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2695 if (SendKeyEventToPlugins(event))
2696 return; // PlugIn did something, and does not want the canvas to do
2697 // anything else
2698
2699 int key_char = event.GetKeyCode();
2700 switch (key_char) {
2701 case '?':
2702 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2703 break;
2704 case '+':
2705 ZoomCanvas(g_plus_minus_zoom_factor, false);
2706 break;
2707 case '-':
2708 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2709 break;
2710 default:
2711 break;
2712 }
2713 if (g_benable_rotate) {
2714 switch (key_char) {
2715 case ']':
2716 RotateCanvas(1);
2717 Refresh();
2718 break;
2719
2720 case '[':
2721 RotateCanvas(-1);
2722 Refresh();
2723 break;
2724
2725 case '\\':
2726 DoRotateCanvas(0);
2727 break;
2728 }
2729 }
2730
2731 event.Skip();
2732}
2733
2734void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2735 if (SendKeyEventToPlugins(event))
2736 return; // PlugIn did something, and does not want the canvas to do
2737 // anything else
2738
2739 bool b_handled = false;
2740
2741 m_modkeys = event.GetModifiers();
2742
2743 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2744
2745#ifdef OCPN_ALT_MENUBAR
2746#ifndef __WXOSX__
2747 // If the permanent menubar is disabled, we show it temporarily when Alt is
2748 // pressed or when Alt + a letter is presssed (for the top-menu-level
2749 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2750 // some special cases.
2751 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2752 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2753 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2754 if (!g_bTempShowMenuBar) {
2755 g_bTempShowMenuBar = true;
2756 parent_frame->ApplyGlobalSettings(false);
2757 }
2758 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2759 event.Skip();
2760 return;
2761 }
2762 // If another key is pressed while Alt is down, do NOT toggle the menus when
2763 // Alt is released
2764 if (event.GetKeyCode() != WXK_ALT) {
2765 m_bMayToggleMenuBar = false;
2766 }
2767 }
2768#endif
2769#endif
2770
2771 // HOTKEYS
2772 switch (event.GetKeyCode()) {
2773 case WXK_TAB:
2774 // parent_frame->SwitchKBFocus( this );
2775 break;
2776
2777 case WXK_MENU:
2778 int x, y;
2779 event.GetPosition(&x, &y);
2780 m_FinishRouteOnKillFocus = false;
2781 CallPopupMenu(x, y);
2782 m_FinishRouteOnKillFocus = true;
2783 break;
2784
2785 case WXK_ALT:
2786 m_modkeys |= wxMOD_ALT;
2787 break;
2788
2789 case WXK_CONTROL:
2790 m_modkeys |= wxMOD_CONTROL;
2791 break;
2792
2793#ifdef __WXOSX__
2794 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2795 case WXK_RAW_CONTROL:
2796 m_modkeys |= wxMOD_RAW_CONTROL;
2797 break;
2798#endif
2799
2800 case WXK_LEFT:
2801 if (m_modkeys == wxMOD_CONTROL)
2802 parent_frame->DoStackDown(this);
2803 else if (g_bsmoothpanzoom) {
2804 StartTimedMovement();
2805 m_panx = -1;
2806 } else {
2807 PanCanvas(-panspeed, 0);
2808 }
2809 b_handled = true;
2810 break;
2811
2812 case WXK_UP:
2813 if (g_bsmoothpanzoom) {
2814 StartTimedMovement();
2815 m_pany = -1;
2816 } else
2817 PanCanvas(0, -panspeed);
2818 b_handled = true;
2819 break;
2820
2821 case WXK_RIGHT:
2822 if (m_modkeys == wxMOD_CONTROL)
2823 parent_frame->DoStackUp(this);
2824 else if (g_bsmoothpanzoom) {
2825 StartTimedMovement();
2826 m_panx = 1;
2827 } else
2828 PanCanvas(panspeed, 0);
2829 b_handled = true;
2830
2831 break;
2832
2833 case WXK_DOWN:
2834 if (g_bsmoothpanzoom) {
2835 StartTimedMovement();
2836 m_pany = 1;
2837 } else
2838 PanCanvas(0, panspeed);
2839 b_handled = true;
2840 break;
2841
2842 case WXK_F2:
2843 TogglebFollow();
2844 break;
2845
2846 case WXK_F3: {
2847 SetShowENCText(!GetShowENCText());
2848 Refresh(true);
2849 InvalidateGL();
2850 break;
2851 }
2852 case WXK_F4:
2853 if (!m_bMeasure_Active) {
2854 if (event.ShiftDown())
2855 m_bMeasure_DistCircle = true;
2856 else
2857 m_bMeasure_DistCircle = false;
2858
2859 StartMeasureRoute();
2860 } else {
2861 CancelMeasureRoute();
2862
2863 SetCursor(*pCursorArrow);
2864
2865 // SurfaceToolbar();
2866 InvalidateGL();
2867 Refresh(false);
2868 }
2869
2870 break;
2871
2872 case WXK_F5:
2873 parent_frame->ToggleColorScheme();
2874 gFrame->Raise();
2875 TriggerDeferredFocus();
2876 break;
2877
2878 case WXK_F6: {
2879 int mod = m_modkeys & wxMOD_SHIFT;
2880 if (mod != m_brightmod) {
2881 m_brightmod = mod;
2882 m_bbrightdir = !m_bbrightdir;
2883 }
2884
2885 if (!m_bbrightdir) {
2886 g_nbrightness -= 10;
2887 if (g_nbrightness <= MIN_BRIGHT) {
2888 g_nbrightness = MIN_BRIGHT;
2889 m_bbrightdir = true;
2890 }
2891 } else {
2892 g_nbrightness += 10;
2893 if (g_nbrightness >= MAX_BRIGHT) {
2894 g_nbrightness = MAX_BRIGHT;
2895 m_bbrightdir = false;
2896 }
2897 }
2898
2899 SetScreenBrightness(g_nbrightness);
2900 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2901
2902 SetFocus(); // just in case the external program steals it....
2903 gFrame->Raise(); // And reactivate the application main
2904
2905 break;
2906 }
2907
2908 case WXK_F7:
2909 parent_frame->DoStackDown(this);
2910 break;
2911
2912 case WXK_F8:
2913 parent_frame->DoStackUp(this);
2914 break;
2915
2916#ifndef __WXOSX__
2917 case WXK_F9: {
2918 double t0 = wxGetLocalTimeMillis().ToDouble();
2919 pConfig->Flush();
2920 double t1 = wxGetLocalTimeMillis().ToDouble() - t0;
2921
2922 ToggleCanvasQuiltMode();
2923 auto &noteman = NotificationManager::GetInstance();
2924 noteman.AddNotification(NotificationSeverity::kCritical,
2925 "Test Notification long message.\nMultiline "
2926 "message that may be many, many chars wide.");
2927
2928 break;
2929 }
2930#endif
2931
2932 case WXK_F11:
2933 parent_frame->ToggleFullScreen();
2934 b_handled = true;
2935 break;
2936
2937 case WXK_F12: {
2938 if (m_modkeys == wxMOD_ALT) {
2939 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2940 // testing
2941 bool b = GetEnableTenHertzUpdate();
2943 UpdateGPSCompassStatusBox(true);
2944 auto &noteman = NotificationManager::GetInstance();
2945 noteman.AddNotification(NotificationSeverity::kInformational,
2946 "Test Timed Notification", 10);
2947 } else {
2948 ToggleChartOutlines();
2949 }
2950 break;
2951 }
2952
2953 case WXK_PAUSE: // Drop MOB
2954 parent_frame->ActivateMOB();
2955 break;
2956
2957 // NUMERIC PAD
2958 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2959 case WXK_PAGEUP: {
2960 ZoomCanvas(g_plus_minus_zoom_factor, false);
2961 break;
2962 }
2963 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2964 case WXK_PAGEDOWN: {
2965 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2966 break;
2967 }
2968 case WXK_DELETE:
2969 case WXK_BACK:
2970 if (m_bMeasure_Active) {
2971 if (m_nMeasureState > 2) {
2972 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2973 m_pMeasureRoute->m_lastMousePointIndex =
2974 m_pMeasureRoute->GetnPoints();
2975 m_nMeasureState--;
2976 gFrame->RefreshAllCanvas();
2977 } else {
2978 CancelMeasureRoute();
2979 StartMeasureRoute();
2980 }
2981 }
2982 break;
2983 default:
2984 break;
2985 }
2986
2987 if (event.GetKeyCode() < 128) // ascii
2988 {
2989 int key_char = event.GetKeyCode();
2990
2991 // Handle both QWERTY and AZERTY keyboard separately for a few control
2992 // codes
2993 if (!g_b_assume_azerty) {
2994#ifdef __WXMAC__
2995 if (g_benable_rotate) {
2996 switch (key_char) {
2997 // On other platforms these are handled in OnKeyChar, which
2998 // (apparently) works better in some locales. On OS X it is better
2999 // to handle them here, since pressing Alt (which should change the
3000 // rotation speed) changes the key char and so prevents the keys
3001 // from working.
3002 case ']':
3003 RotateCanvas(1);
3004 b_handled = true;
3005 break;
3006
3007 case '[':
3008 RotateCanvas(-1);
3009 b_handled = true;
3010 break;
3011
3012 case '\\':
3013 DoRotateCanvas(0);
3014 b_handled = true;
3015 break;
3016 }
3017 }
3018#endif
3019 } else { // AZERTY
3020 switch (key_char) {
3021 case 43:
3022 ZoomCanvas(g_plus_minus_zoom_factor, false);
3023 break;
3024
3025 case 54: // '-' alpha/num pad
3026 // case 56: // '_' alpha/num pad
3027 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
3028 break;
3029 }
3030 }
3031
3032#ifdef __WXOSX__
3033 // Ctrl+Cmd+F toggles fullscreen on macOS
3034 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
3035 m_modkeys & wxMOD_RAW_CONTROL) {
3036 parent_frame->ToggleFullScreen();
3037 return;
3038 }
3039#endif
3040
3041 if (event.ControlDown()) key_char -= 64;
3042
3043 if (key_char >= '0' && key_char <= '9')
3044 SetGroupIndex(key_char - '0');
3045 else
3046
3047 switch (key_char) {
3048 case 'A':
3049 SetShowENCAnchor(!GetShowENCAnchor());
3050 ReloadVP();
3051
3052 break;
3053
3054 case 'C':
3055 parent_frame->ToggleColorScheme();
3056 break;
3057
3058 case 'D': {
3059 int x, y;
3060 event.GetPosition(&x, &y);
3061 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3062 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3063 // First find out what kind of chart is being used
3064 if (!pPopupDetailSlider) {
3065 if (VPoint.b_quilt) {
3066 if (m_pQuilt) {
3067 if (m_pQuilt->GetChartAtPix(
3068 VPoint,
3069 wxPoint(
3070 x, y))) // = null if no chart loaded for this point
3071 {
3072 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3073 ->GetChartType();
3074 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3075 ->GetChartFamily();
3076 }
3077 }
3078 } else {
3079 if (m_singleChart) {
3080 ChartType = m_singleChart->GetChartType();
3081 ChartFam = m_singleChart->GetChartFamily();
3082 }
3083 }
3084 // If a charttype is found show the popupslider
3085 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3086 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3087 pPopupDetailSlider = new PopUpDSlide(
3088 this, -1, ChartType, ChartFam,
3089 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3090 wxDefaultSize, wxSIMPLE_BORDER, _T(""));
3091 if (pPopupDetailSlider) pPopupDetailSlider->Show();
3092 }
3093 } else //( !pPopupDetailSlider ) close popupslider
3094 {
3095 if (pPopupDetailSlider) pPopupDetailSlider->Close();
3096 pPopupDetailSlider = NULL;
3097 }
3098 break;
3099 }
3100
3101 case 'E':
3102 m_nmea_log->Show();
3103 break;
3104
3105 case 'L':
3106 SetShowENCLights(!GetShowENCLights());
3107 ReloadVP();
3108
3109 break;
3110
3111 case 'M':
3112 if (event.ShiftDown())
3113 m_bMeasure_DistCircle = true;
3114 else
3115 m_bMeasure_DistCircle = false;
3116
3117 StartMeasureRoute();
3118 break;
3119
3120 case 'N':
3121 if (g_bInlandEcdis && ps52plib) {
3122 SetENCDisplayCategory((_DisCat)STANDARD);
3123 }
3124 break;
3125
3126 case 'O':
3127 ToggleChartOutlines();
3128 break;
3129
3130 case 'Q':
3131 ToggleCanvasQuiltMode();
3132 break;
3133
3134 case 'P':
3135 parent_frame->ToggleTestPause();
3136 break;
3137 case 'R':
3138 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3139 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3140 g_iNavAidRadarRingsNumberVisible = 1;
3141 else if (!g_bNavAidRadarRingsShown &&
3142 g_iNavAidRadarRingsNumberVisible == 1)
3143 g_iNavAidRadarRingsNumberVisible = 0;
3144 break;
3145 case 'S':
3146 SetShowENCDepth(!m_encShowDepth);
3147 ReloadVP();
3148 break;
3149
3150 case 'T':
3151 SetShowENCText(!GetShowENCText());
3152 ReloadVP();
3153 break;
3154
3155 case 'U':
3156 SetShowENCDataQual(!GetShowENCDataQual());
3157 ReloadVP();
3158 break;
3159
3160 case 'V':
3161 m_bShowNavobjects = !m_bShowNavobjects;
3162 Refresh(true);
3163 break;
3164
3165 case 'W': // W Toggle CPA alarm
3166 ToggleCPAWarn();
3167
3168 break;
3169
3170 case 1: // Ctrl A
3171 TogglebFollow();
3172
3173 break;
3174
3175 case 2: // Ctrl B
3176 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3177 break;
3178
3179 case 13: // Ctrl M // Drop Marker at cursor
3180 {
3181 if (event.ControlDown()) gFrame->DropMarker(false);
3182 break;
3183 }
3184
3185 case 14: // Ctrl N - Activate next waypoint in a route
3186 {
3187 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3188 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3189 if ((indexActive + 1) <= r->GetnPoints()) {
3190 g_pRouteMan->ActivateNextPoint(r, true);
3191 InvalidateGL();
3192 Refresh(false);
3193 }
3194 }
3195 break;
3196 }
3197
3198 case 15: // Ctrl O - Drop Marker at boat's position
3199 {
3200 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3201 break;
3202 }
3203
3204 case 32: // Special needs use space bar
3205 {
3206 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3207 break;
3208 }
3209
3210 case -32: // Ctrl Space // Drop MOB
3211 {
3212 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3213
3214 break;
3215 }
3216
3217 case -20: // Ctrl ,
3218 {
3219 parent_frame->DoSettings();
3220 break;
3221 }
3222 case 17: // Ctrl Q
3223 parent_frame->Close();
3224 return;
3225
3226 case 18: // Ctrl R
3227 StartRoute();
3228 return;
3229
3230 case 20: // Ctrl T
3231 if (NULL == pGoToPositionDialog) // There is one global instance of
3232 // the Go To Position Dialog
3233 pGoToPositionDialog = new GoToPositionDialog(this);
3234 pGoToPositionDialog->SetCanvas(this);
3235 pGoToPositionDialog->Show();
3236 break;
3237
3238 case 25: // Ctrl Y
3239 if (undo->AnythingToRedo()) {
3240 undo->RedoNextAction();
3241 InvalidateGL();
3242 Refresh(false);
3243 }
3244 break;
3245
3246 case 26:
3247 if (event.ShiftDown()) { // Shift-Ctrl-Z
3248 if (undo->AnythingToRedo()) {
3249 undo->RedoNextAction();
3250 InvalidateGL();
3251 Refresh(false);
3252 }
3253 } else { // Ctrl Z
3254 if (undo->AnythingToUndo()) {
3255 undo->UndoLastAction();
3256 InvalidateGL();
3257 Refresh(false);
3258 }
3259 }
3260 break;
3261
3262 case 27:
3263 // Generic break
3264 if (m_bMeasure_Active) {
3265 CancelMeasureRoute();
3266
3267 SetCursor(*pCursorArrow);
3268
3269 // SurfaceToolbar();
3270 gFrame->RefreshAllCanvas();
3271 }
3272
3273 if (m_routeState) // creating route?
3274 {
3275 FinishRoute();
3276 // SurfaceToolbar();
3277 InvalidateGL();
3278 Refresh(false);
3279 }
3280
3281 break;
3282
3283 case 7: // Ctrl G
3284 switch (gamma_state) {
3285 case (0):
3286 r_gamma_mult = 0;
3287 g_gamma_mult = 1;
3288 b_gamma_mult = 0;
3289 gamma_state = 1;
3290 break;
3291 case (1):
3292 r_gamma_mult = 1;
3293 g_gamma_mult = 0;
3294 b_gamma_mult = 0;
3295 gamma_state = 2;
3296 break;
3297 case (2):
3298 r_gamma_mult = 1;
3299 g_gamma_mult = 1;
3300 b_gamma_mult = 1;
3301 gamma_state = 0;
3302 break;
3303 }
3304 SetScreenBrightness(g_nbrightness);
3305
3306 break;
3307
3308 case 9: // Ctrl I
3309 if (event.ControlDown()) {
3310 m_bShowCompassWin = !m_bShowCompassWin;
3311 SetShowGPSCompassWindow(m_bShowCompassWin);
3312 Refresh(false);
3313 }
3314 break;
3315
3316 default:
3317 break;
3318
3319 } // switch
3320 }
3321
3322 // Allow OnKeyChar to catch the key events too.
3323 if (!b_handled) {
3324 event.Skip();
3325 }
3326}
3327
3328void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3329 if (SendKeyEventToPlugins(event))
3330 return; // PlugIn did something, and does not want the canvas to do
3331 // anything else
3332
3333 switch (event.GetKeyCode()) {
3334 case WXK_TAB:
3335 parent_frame->SwitchKBFocus(this);
3336 break;
3337
3338 case WXK_LEFT:
3339 case WXK_RIGHT:
3340 m_panx = 0;
3341 if (!m_pany) m_panspeed = 0;
3342 break;
3343
3344 case WXK_UP:
3345 case WXK_DOWN:
3346 m_pany = 0;
3347 if (!m_panx) m_panspeed = 0;
3348 break;
3349
3350 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3351 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3352 case WXK_PAGEUP:
3353 case WXK_PAGEDOWN:
3354 if (m_mustmove) DoMovement(m_mustmove);
3355
3356 m_zoom_factor = 1;
3357 break;
3358
3359 case WXK_ALT:
3360 m_modkeys &= ~wxMOD_ALT;
3361#ifdef OCPN_ALT_MENUBAR
3362#ifndef __WXOSX__
3363 // If the permanent menu bar is disabled, and we are not in the middle of
3364 // another key combo, then show the menu bar temporarily when Alt is
3365 // released (or hide it if already visible).
3366 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3367 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3368 parent_frame->ApplyGlobalSettings(false);
3369 }
3370 m_bMayToggleMenuBar = true;
3371#endif
3372#endif
3373 break;
3374
3375 case WXK_CONTROL:
3376 m_modkeys &= ~wxMOD_CONTROL;
3377 break;
3378 }
3379
3380 if (event.GetKeyCode() < 128) // ascii
3381 {
3382 int key_char = event.GetKeyCode();
3383
3384 // Handle both QWERTY and AZERTY keyboard separately for a few control
3385 // codes
3386 if (!g_b_assume_azerty) {
3387 switch (key_char) {
3388 case '+':
3389 case '=':
3390 case '-':
3391 case '_':
3392 case 54:
3393 case 56: // '_' alpha/num pad
3394 DoMovement(m_mustmove);
3395
3396 // m_zoom_factor = 1;
3397 break;
3398 case '[':
3399 case ']':
3400 DoMovement(m_mustmove);
3401 m_rotation_speed = 0;
3402 break;
3403 }
3404 } else {
3405 switch (key_char) {
3406 case 43:
3407 case 54: // '-' alpha/num pad
3408 case 56: // '_' alpha/num pad
3409 DoMovement(m_mustmove);
3410
3411 m_zoom_factor = 1;
3412 break;
3413 }
3414 }
3415 }
3416 event.Skip();
3417}
3418
3419void ChartCanvas::ToggleChartOutlines(void) {
3420 m_bShowOutlines = !m_bShowOutlines;
3421
3422 Refresh(false);
3423
3424#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3425 // needs a full refresh
3426 if (g_bopengl) InvalidateGL();
3427#endif
3428}
3429
3430void ChartCanvas::ToggleLookahead() {
3431 m_bLookAhead = !m_bLookAhead;
3432 m_OSoffsetx = 0; // center ownship
3433 m_OSoffsety = 0;
3434}
3435
3436void ChartCanvas::SetUpMode(int mode) {
3437 m_upMode = mode;
3438
3439 if (mode != NORTH_UP_MODE) {
3440 // Stuff the COGAvg table in case COGUp is selected
3441 double stuff = 0;
3442 if (!std::isnan(gCog)) stuff = gCog;
3443
3444 if (g_COGAvgSec > 0) {
3445 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3446 }
3447 g_COGAvg = stuff;
3448 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3449 } else {
3450 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3451 SetVPRotation(GetVPSkew());
3452 else
3453 SetVPRotation(0); /* reset to north up */
3454 }
3455
3456 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3457 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3458
3459 UpdateGPSCompassStatusBox(true);
3460 gFrame->DoChartUpdate();
3461}
3462
3463bool ChartCanvas::DoCanvasCOGSet(void) {
3464 if (GetUpMode() == NORTH_UP_MODE) return false;
3465 double cog_use = g_COGAvg;
3466 if (g_btenhertz) cog_use = gCog;
3467
3468 double rotation = 0;
3469 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3470 rotation = -gHdt * PI / 180.;
3471 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3472 rotation = -cog_use * PI / 180.;
3473
3474 SetVPRotation(rotation);
3475 return true;
3476}
3477
3478double easeOutCubic(double t) {
3479 // Starts quickly and slows down toward the end
3480 return 1.0 - pow(1.0 - t, 3.0);
3481}
3482
3483void ChartCanvas::StartChartDragInertia() {
3484 //
3485 // printf("\nStart ChartDragInertia\n");
3486 m_bChartDragging = false;
3487
3488 // Set some parameters
3489 m_chart_drag_inertia_time = 750; // msec
3490 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3491 m_last_elapsed = 0;
3492
3493 // Calculate ending drag velocity
3494 size_t n_vel = 10;
3495 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3496 int xacc = 0;
3497 int yacc = 0;
3498 double tacc = 0;
3499 size_t length = m_drag_vec_t.size();
3500 for (size_t i = 0; i < n_vel; i++) {
3501 xacc += m_drag_vec_x.at(length - 1 - i);
3502 yacc += m_drag_vec_y.at(length - 1 - i);
3503 tacc += m_drag_vec_t.at(length - 1 - i);
3504 // printf("%d %g\n", xacc, tacc);
3505 }
3506 m_chart_drag_velocity_x = xacc / tacc;
3507 m_chart_drag_velocity_y = yacc / tacc;
3508
3509 m_chart_drag_inertia_active = true;
3510
3511 // First callback as fast as possible.
3512 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3513
3514 // printf(" Drag parms %d %d %g\n", m_chart_drag_total_x,
3515 // m_chart_drag_total_y, m_chart_drag_total_time);
3516}
3517
3518void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3519 if (!m_chart_drag_inertia_active) return;
3520
3521 // Calculate time fraction from 0..1
3522 wxLongLong now = wxGetLocalTimeMillis();
3523 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3524 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3525 if (t > 1.0) t = 1.0;
3526 double e = 1.0 - easeOutCubic(t); // 0..1
3527
3528 double dx =
3529 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3530 double dy =
3531 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3532
3533 // double distance = pow((pow(dx, 2) + pow(dy, 2)), 0.5);
3534 // printf(" %5g %5g %5g %5g pix/sec\n", elapsed,
3535 // elapsed - m_last_elapsed, distance, distance * 1000 / elapsed);
3536
3537 m_last_elapsed = elapsed;
3538
3539 // Ensure that target destination lies on whole-pixel boundary
3540 // This allows the render engine to use a faster FBO copy method for drawing
3541 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3542 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3543 double inertia_lat, inertia_lon;
3544 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3545 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3546
3547 Refresh(false);
3548
3549 // Stop condition
3550 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3551 m_chart_drag_inertia_timer.Stop();
3552 m_chart_drag_inertia_active = false;
3553
3554 // Disable chart pan movement logic
3555 m_target_lat = GetVP().clat;
3556 m_target_lon = GetVP().clon;
3557 m_pan_drag.x = m_pan_drag.y = 0;
3558 m_panx = m_pany = 0;
3559
3560 } else {
3561 int target_redraw_interval = 40; // msec
3562 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3563 }
3564}
3565
3566void ChartCanvas::StopMovement() {
3567 m_panx = m_pany = 0;
3568 m_panspeed = 0;
3569 m_zoom_factor = 1;
3570 m_rotation_speed = 0;
3571 m_mustmove = 0;
3572#if 0
3573#if !defined(__WXGTK__) && !defined(__WXQT__)
3574 SetFocus();
3575 gFrame->Raise();
3576#endif
3577#endif
3578}
3579
3580/* instead of integrating in timer callbacks
3581 (which do not always get called fast enough)
3582 we can perform the integration of movement
3583 at each render frame based on the time change */
3584bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3585 // Start/restart the stop movement timer
3586 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3587
3588 if (!pMovementTimer->IsRunning()) {
3589 // printf("timer not running, starting\n");
3590 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3591 }
3592
3593 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3594 // already moving, gets called again because of key-repeat event
3595 return false;
3596 }
3597
3598 m_last_movement_time = wxDateTime::UNow();
3599
3600 return true;
3601}
3602void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3603 int nstep) {
3604 // Save the target
3605 m_target_lat = target_lat;
3606 m_target_lon = target_lon;
3607
3608 // Save the start point
3609 m_start_lat = GetVP().clat;
3610 m_start_lon = GetVP().clon;
3611
3612 m_VPMovementTimer.Start(1, true); // oneshot
3613 m_timed_move_vp_active = true;
3614 m_stvpc = 0;
3615 m_timedVP_step = nstep;
3616}
3617
3618void ChartCanvas::DoTimedMovementVP() {
3619 if (!m_timed_move_vp_active) return; // not active
3620 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3621 StopMovement();
3622 return;
3623 }
3624 // Stop condition
3625 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3626 double d2 =
3627 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3628 d2 = pow(d2, 0.5);
3629
3630 if (d2 < one_pix) {
3631 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3632 StopMovementVP();
3633 return;
3634 }
3635
3636 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3637 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3638 // StopMovementVP();
3639 // return;
3640 // }
3641
3642 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3643 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3644
3645 m_run_lat = new_lat;
3646 m_run_lon = new_lon;
3647
3648 // printf(" Timed\n");
3649 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3650}
3651
3652void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3653
3654void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3655
3656void ChartCanvas::StartTimedMovementTarget() {}
3657
3658void ChartCanvas::DoTimedMovementTarget() {}
3659
3660void ChartCanvas::StopMovementTarget() {}
3661
3662void ChartCanvas::DoTimedMovement() {
3663 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3664 !m_rotation_speed)
3665 return; /* not moving */
3666
3667 wxDateTime now = wxDateTime::UNow();
3668 long dt = 0;
3669 if (m_last_movement_time.IsValid())
3670 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3671
3672 m_last_movement_time = now;
3673
3674 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3675 dt = 500;
3676
3677 DoMovement(dt);
3678}
3679
3680void ChartCanvas::DoMovement(long dt) {
3681 /* if we get here quickly assume 1ms so that some movement occurs */
3682 if (dt == 0) dt = 1;
3683
3684 m_mustmove -= dt;
3685 if (m_mustmove < 0) m_mustmove = 0;
3686
3687 if (m_pan_drag.x || m_pan_drag.y) {
3688 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3689 m_pan_drag.x = m_pan_drag.y = 0;
3690 }
3691
3692 if (m_panx || m_pany) {
3693 const double slowpan = .1, maxpan = 2;
3694 if (m_modkeys == wxMOD_ALT)
3695 m_panspeed = slowpan;
3696 else {
3697 m_panspeed += (double)dt / 500; /* apply acceleration */
3698 m_panspeed = wxMin(maxpan, m_panspeed);
3699 }
3700 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3701 }
3702
3703 if (m_zoom_factor != 1) {
3704 double alpha = 400, beta = 1.5;
3705 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3706
3707 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3708
3709 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3710
3711 // Try to hit the zoom target exactly.
3712 // if(m_wheelzoom_stop_oneshot > 0)
3713 {
3714 if (zoom_factor > 1) {
3715 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3716 zoom_factor = VPoint.chart_scale / m_zoom_target;
3717 }
3718
3719 else if (zoom_factor < 1) {
3720 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3721 zoom_factor = VPoint.chart_scale / m_zoom_target;
3722 }
3723 }
3724
3725 if (fabs(zoom_factor - 1) > 1e-4)
3726 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3727
3728 if (m_wheelzoom_stop_oneshot > 0) {
3729 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3730 m_wheelzoom_stop_oneshot = 0;
3731 StopMovement();
3732 }
3733
3734 // Don't overshoot the zoom target.
3735 if (zoom_factor > 1) {
3736 if (VPoint.chart_scale <= m_zoom_target) {
3737 m_wheelzoom_stop_oneshot = 0;
3738 StopMovement();
3739 }
3740 } else if (zoom_factor < 1) {
3741 if (VPoint.chart_scale >= m_zoom_target) {
3742 m_wheelzoom_stop_oneshot = 0;
3743 StopMovement();
3744 }
3745 }
3746 }
3747 }
3748
3749 if (m_rotation_speed) { /* in degrees per second */
3750 double speed = m_rotation_speed;
3751 if (m_modkeys == wxMOD_ALT) speed /= 10;
3752 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3753 }
3754}
3755
3756void ChartCanvas::SetColorScheme(ColorScheme cs) {
3757 SetAlertString(_T(""));
3758
3759 // Setup ownship image pointers
3760 switch (cs) {
3761 case GLOBAL_COLOR_SCHEME_DAY:
3762 m_pos_image_red = &m_os_image_red_day;
3763 m_pos_image_grey = &m_os_image_grey_day;
3764 m_pos_image_yellow = &m_os_image_yellow_day;
3765 m_pos_image_user = m_pos_image_user_day;
3766 m_pos_image_user_grey = m_pos_image_user_grey_day;
3767 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3768 m_cTideBitmap = m_bmTideDay;
3769 m_cCurrentBitmap = m_bmCurrentDay;
3770
3771 break;
3772 case GLOBAL_COLOR_SCHEME_DUSK:
3773 m_pos_image_red = &m_os_image_red_dusk;
3774 m_pos_image_grey = &m_os_image_grey_dusk;
3775 m_pos_image_yellow = &m_os_image_yellow_dusk;
3776 m_pos_image_user = m_pos_image_user_dusk;
3777 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3778 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3779 m_cTideBitmap = m_bmTideDusk;
3780 m_cCurrentBitmap = m_bmCurrentDusk;
3781 break;
3782 case GLOBAL_COLOR_SCHEME_NIGHT:
3783 m_pos_image_red = &m_os_image_red_night;
3784 m_pos_image_grey = &m_os_image_grey_night;
3785 m_pos_image_yellow = &m_os_image_yellow_night;
3786 m_pos_image_user = m_pos_image_user_night;
3787 m_pos_image_user_grey = m_pos_image_user_grey_night;
3788 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3789 m_cTideBitmap = m_bmTideNight;
3790 m_cCurrentBitmap = m_bmCurrentNight;
3791 break;
3792 default:
3793 m_pos_image_red = &m_os_image_red_day;
3794 m_pos_image_grey = &m_os_image_grey_day;
3795 m_pos_image_yellow = &m_os_image_yellow_day;
3796 m_pos_image_user = m_pos_image_user_day;
3797 m_pos_image_user_grey = m_pos_image_user_grey_day;
3798 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3799 m_cTideBitmap = m_bmTideDay;
3800 m_cCurrentBitmap = m_bmCurrentDay;
3801 break;
3802 }
3803
3804 CreateDepthUnitEmbossMaps(cs);
3805 CreateOZEmbossMapData(cs);
3806
3807 // Set up fog effect base color
3808 m_fog_color = wxColor(
3809 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3810 float dim = 1.0;
3811 switch (cs) {
3812 case GLOBAL_COLOR_SCHEME_DUSK:
3813 dim = 0.5;
3814 break;
3815 case GLOBAL_COLOR_SCHEME_NIGHT:
3816 dim = 0.25;
3817 break;
3818 default:
3819 break;
3820 }
3821 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3822 m_fog_color.Blue() * dim);
3823
3824 // Really dark
3825#if 0
3826 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3827 SetBackgroundColour( wxColour(0,0,0) );
3828
3829 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3830 }
3831 else{
3832 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3833#ifndef __WXMAC__
3834 SetBackgroundColour( wxNullColour );
3835#endif
3836 }
3837#endif
3838
3839 // UpdateToolbarColorScheme(cs);
3840
3841 m_Piano->SetColorScheme(cs);
3842
3843 m_Compass->SetColorScheme(cs);
3844
3845 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3846
3847 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3848#ifdef ocpnUSE_GL
3849 if (g_bopengl && m_glcc) {
3850 m_glcc->SetColorScheme(cs);
3851 g_glTextureManager->ClearAllRasterTextures();
3852 // m_glcc->FlushFBO();
3853 }
3854#endif
3855 SetbTCUpdate(true); // force re-render of tide/current locators
3856 m_brepaint_piano = true;
3857
3858 ReloadVP();
3859
3860 m_cs = cs;
3861}
3862
3863wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3864 wxImage img = Bitmap.ConvertToImage();
3865 int sx = img.GetWidth();
3866 int sy = img.GetHeight();
3867
3868 wxImage new_img(img);
3869
3870 for (int i = 0; i < sx; i++) {
3871 for (int j = 0; j < sy; j++) {
3872 if (!img.IsTransparent(i, j)) {
3873 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3874 (unsigned char)(img.GetGreen(i, j) * factor),
3875 (unsigned char)(img.GetBlue(i, j) * factor));
3876 }
3877 }
3878 }
3879
3880 wxBitmap ret = wxBitmap(new_img);
3881
3882 return ret;
3883}
3884
3885void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3886 int max) {
3887 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3888 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3889
3890 if (!m_pBrightPopup) {
3891 // Calculate size
3892 int x, y;
3893 GetTextExtent(_T("MAX"), &x, &y, NULL, NULL, pfont);
3894
3895 m_pBrightPopup = new TimedPopupWin(this, 3);
3896
3897 m_pBrightPopup->SetSize(x, y);
3898 m_pBrightPopup->Move(120, 120);
3899 }
3900
3901 int bmpsx = m_pBrightPopup->GetSize().x;
3902 int bmpsy = m_pBrightPopup->GetSize().y;
3903
3904 wxBitmap bmp(bmpsx, bmpsx);
3905 wxMemoryDC mdc(bmp);
3906
3907 mdc.SetTextForeground(GetGlobalColor(_T("GREEN4")));
3908 mdc.SetBackground(wxBrush(GetGlobalColor(_T("UINFD"))));
3909 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3910 mdc.SetBrush(wxBrush(GetGlobalColor(_T("UINFD"))));
3911 mdc.Clear();
3912
3913 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3914
3915 mdc.SetFont(*pfont);
3916 wxString val;
3917
3918 if (brightness == max)
3919 val = _T("MAX");
3920 else if (brightness == min)
3921 val = _T("MIN");
3922 else
3923 val.Printf(_T("%3d"), brightness);
3924
3925 mdc.DrawText(val, 0, 0);
3926
3927 mdc.SelectObject(wxNullBitmap);
3928
3929 m_pBrightPopup->SetBitmap(bmp);
3930 m_pBrightPopup->Show();
3931 m_pBrightPopup->Refresh();
3932}
3933
3934void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3935 m_b_rot_hidef = true;
3936 ReloadVP();
3937}
3938
3939void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3940 if (!g_bRollover) return;
3941
3942 bool b_need_refresh = false;
3943
3944 wxSize win_size = GetSize() * m_displayScale;
3945 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3946
3947 // Handle the AIS Rollover Window first
3948 bool showAISRollover = false;
3949 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3950 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3951 SelectItem *pFind = pSelectAIS->FindSelection(
3952 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3953 if (pFind) {
3954 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3955 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3956
3957 if (ptarget) {
3958 showAISRollover = true;
3959
3960 if (NULL == m_pAISRolloverWin) {
3961 m_pAISRolloverWin = new RolloverWin(this);
3962 m_pAISRolloverWin->IsActive(false);
3963 b_need_refresh = true;
3964 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3965 m_AISRollover_MMSI != FoundAIS_MMSI) {
3966 // Sometimes the mouse moves fast enough to get over a new AIS
3967 // target before the one-shot has fired to remove the old target.
3968 // Result: wrong target data is shown.
3969 // Detect this case,close the existing rollover ASAP, and restart
3970 // the timer.
3971 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3972 m_pAISRolloverWin->IsActive(false);
3973 m_AISRollover_MMSI = 0;
3974 Refresh();
3975 return;
3976 }
3977
3978 m_AISRollover_MMSI = FoundAIS_MMSI;
3979
3980 if (!m_pAISRolloverWin->IsActive()) {
3981 wxString s = ptarget->GetRolloverString();
3982 m_pAISRolloverWin->SetString(s);
3983
3984 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3985 AIS_ROLLOVER, win_size);
3986 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3987 m_pAISRolloverWin->IsActive(true);
3988 b_need_refresh = true;
3989 }
3990 }
3991 } else {
3992 m_AISRollover_MMSI = 0;
3993 showAISRollover = false;
3994 }
3995 }
3996
3997 // Maybe turn the rollover off
3998 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3999 m_pAISRolloverWin->IsActive(false);
4000 m_AISRollover_MMSI = 0;
4001 b_need_refresh = true;
4002 }
4003
4004 // Now the Route info rollover
4005 // Show the route segment info
4006 bool showRouteRollover = false;
4007
4008 if (NULL == m_pRolloverRouteSeg) {
4009 // Get a list of all selectable sgements, and search for the first
4010 // visible segment as the rollover target.
4011
4012 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4013 SelectableItemList SelList = pSelect->FindSelectionList(
4014 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
4015 wxSelectableItemListNode *node = SelList.GetFirst();
4016 while (node) {
4017 SelectItem *pFindSel = node->GetData();
4018
4019 Route *pr = (Route *)pFindSel->m_pData3; // candidate
4020
4021 if (pr && pr->IsVisible()) {
4022 m_pRolloverRouteSeg = pFindSel;
4023 showRouteRollover = true;
4024
4025 if (NULL == m_pRouteRolloverWin) {
4026 m_pRouteRolloverWin = new RolloverWin(this, 10);
4027 m_pRouteRolloverWin->IsActive(false);
4028 }
4029
4030 if (!m_pRouteRolloverWin->IsActive()) {
4031 wxString s;
4032 RoutePoint *segShow_point_a =
4033 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4034 RoutePoint *segShow_point_b =
4035 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4036
4037 double brg, dist;
4038 DistanceBearingMercator(
4039 segShow_point_b->m_lat, segShow_point_b->m_lon,
4040 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4041
4042 if (!pr->m_bIsInLayer)
4043 s.Append(_("Route") + _T(": "));
4044 else
4045 s.Append(_("Layer Route: "));
4046
4047 if (pr->m_RouteNameString.IsEmpty())
4048 s.Append(_("(unnamed)"));
4049 else
4050 s.Append(pr->m_RouteNameString);
4051
4052 s << _T("\n") << _("Total Length: ")
4053 << FormatDistanceAdaptive(pr->m_route_length) << _T("\n")
4054 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4055 << segShow_point_b->GetName() << _T("\n");
4056
4057 if (g_bShowTrue)
4058 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4059 (int)floor(brg + 0.5), 0x00B0);
4060 if (g_bShowMag) {
4061 double latAverage =
4062 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4063 double lonAverage =
4064 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4065 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4066
4067 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4068 (int)floor(varBrg + 0.5), 0x00B0);
4069 }
4070
4071 s << FormatDistanceAdaptive(dist);
4072
4073 // Compute and display cumulative distance from route start point to
4074 // current leg end point and RNG,TTG,ETA from ship to current leg end
4075 // point for active route
4076 double shiptoEndLeg = 0.;
4077 bool validActive = false;
4078 if (pr->IsActive() &&
4079 pr->pRoutePointList->GetFirst()->GetData()->m_bIsActive)
4080 validActive = true;
4081
4082 if (segShow_point_a != pr->pRoutePointList->GetFirst()->GetData()) {
4083 wxRoutePointListNode *node =
4084 (pr->pRoutePointList)->GetFirst()->GetNext();
4085 RoutePoint *prp;
4086 float dist_to_endleg = 0;
4087 wxString t;
4088
4089 while (node) {
4090 prp = node->GetData();
4091 if (validActive)
4092 shiptoEndLeg += prp->m_seg_len;
4093 else if (prp->m_bIsActive)
4094 validActive = true;
4095 dist_to_endleg += prp->m_seg_len;
4096 if (prp->IsSame(segShow_point_a)) break;
4097 node = node->GetNext();
4098 }
4099 s << _T(" (+") << FormatDistanceAdaptive(dist_to_endleg) << _T(")");
4100 }
4101 // write from ship to end selected leg point data if the route is
4102 // active
4103 if (validActive) {
4104 s << _T("\n") << _("From Ship To") << _T(" ")
4105 << segShow_point_b->GetName() << _T("\n");
4106 shiptoEndLeg +=
4107 g_pRouteMan
4108 ->GetCurrentRngToActivePoint(); // add distance from ship
4109 // to active point
4110 shiptoEndLeg +=
4111 segShow_point_b
4112 ->m_seg_len; // add the lenght of the selected leg
4113 s << FormatDistanceAdaptive(shiptoEndLeg);
4114 // ensure sog/cog are valid and vmg is positive to keep data
4115 // coherent
4116 double vmg = 0.;
4117 if (!std::isnan(gCog) && !std::isnan(gSog))
4118 vmg = gSog *
4119 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4120 PI / 180.);
4121 if (vmg > 0.) {
4122 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4123 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4124 s << _T(" - ")
4125 << wxString(ttg_sec > SECONDS_PER_DAY
4126 ? ttg_span.Format(_("%Dd %H:%M"))
4127 : ttg_span.Format(_("%H:%M")));
4128 wxDateTime dtnow, eta;
4129 eta = dtnow.SetToCurrent().Add(ttg_span);
4130 s << _T(" - ") << eta.Format(_T("%b")).Mid(0, 4)
4131 << eta.Format(_T(" %d %H:%M"));
4132 } else
4133 s << _T(" ---- ----");
4134 }
4135 m_pRouteRolloverWin->SetString(s);
4136
4137 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4138 LEG_ROLLOVER, win_size);
4139 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4140 m_pRouteRolloverWin->IsActive(true);
4141 b_need_refresh = true;
4142 showRouteRollover = true;
4143 break;
4144 }
4145 } else
4146 node = node->GetNext();
4147 }
4148 } else {
4149 // Is the cursor still in select radius, and not timed out?
4150 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4151 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4152 m_pRolloverRouteSeg))
4153 showRouteRollover = false;
4154 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4155 showRouteRollover = false;
4156 else
4157 showRouteRollover = true;
4158 }
4159
4160 // If currently creating a route, do not show this rollover window
4161 if (m_routeState) showRouteRollover = false;
4162
4163 // Similar for AIS target rollover window
4164 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4165 showRouteRollover = false;
4166
4167 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4168 !showRouteRollover) {
4169 m_pRouteRolloverWin->IsActive(false);
4170 m_pRolloverRouteSeg = NULL;
4171 m_pRouteRolloverWin->Destroy();
4172 m_pRouteRolloverWin = NULL;
4173 b_need_refresh = true;
4174 } else if (m_pRouteRolloverWin && showRouteRollover) {
4175 m_pRouteRolloverWin->IsActive(true);
4176 b_need_refresh = true;
4177 }
4178
4179 // Now the Track info rollover
4180 // Show the track segment info
4181 bool showTrackRollover = false;
4182
4183 if (NULL == m_pRolloverTrackSeg) {
4184 // Get a list of all selectable sgements, and search for the first
4185 // visible segment as the rollover target.
4186
4187 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4188 SelectableItemList SelList = pSelect->FindSelectionList(
4189 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4190 wxSelectableItemListNode *node = SelList.GetFirst();
4191 while (node) {
4192 SelectItem *pFindSel = node->GetData();
4193
4194 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4195
4196 if (pt && pt->IsVisible()) {
4197 m_pRolloverTrackSeg = pFindSel;
4198 showTrackRollover = true;
4199
4200 if (NULL == m_pTrackRolloverWin) {
4201 m_pTrackRolloverWin = new RolloverWin(this, 10);
4202 m_pTrackRolloverWin->IsActive(false);
4203 }
4204
4205 if (!m_pTrackRolloverWin->IsActive()) {
4206 wxString s;
4207 TrackPoint *segShow_point_a =
4208 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4209 TrackPoint *segShow_point_b =
4210 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4211
4212 double brg, dist;
4213 DistanceBearingMercator(
4214 segShow_point_b->m_lat, segShow_point_b->m_lon,
4215 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4216
4217 if (!pt->m_bIsInLayer)
4218 s.Append(_("Track") + _T(": "));
4219 else
4220 s.Append(_("Layer Track: "));
4221
4222 if (pt->GetName().IsEmpty())
4223 s.Append(_("(unnamed)"));
4224 else
4225 s.Append(pt->GetName());
4226 double tlenght = pt->Length();
4227 s << _T("\n") << _("Total Track: ")
4228 << FormatDistanceAdaptive(tlenght);
4229 if (pt->GetLastPoint()->GetTimeString() &&
4230 pt->GetPoint(0)->GetTimeString()) {
4231 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4232 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4233 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4234 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4235 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4236 s << wxString::Format(_T(" %.1f "), (float)(tlenght / htime))
4237 << getUsrSpeedUnit();
4238 s << wxString(htime > 24. ? ttime.Format(_T(" %Dd %H:%M"))
4239 : ttime.Format(_T(" %H:%M")));
4240 }
4241 }
4242
4243 if (g_bShowTrackPointTime && strlen(segShow_point_b->GetTimeString()))
4244 s << _T("\n") << _("Segment Created: ")
4245 << segShow_point_b->GetTimeString();
4246
4247 s << _T("\n");
4248 if (g_bShowTrue)
4249 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4250 0x00B0);
4251
4252 if (g_bShowMag) {
4253 double latAverage =
4254 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4255 double lonAverage =
4256 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4257 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4258
4259 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4260 0x00B0);
4261 }
4262
4263 s << FormatDistanceAdaptive(dist);
4264
4265 if (segShow_point_a->GetTimeString() &&
4266 segShow_point_b->GetTimeString()) {
4267 wxDateTime apoint = segShow_point_a->GetCreateTime();
4268 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4269 if (apoint.IsValid() && bpoint.IsValid()) {
4270 double segmentSpeed = toUsrSpeed(
4271 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4272 s << wxString::Format(_T(" %.1f "), (float)segmentSpeed)
4273 << getUsrSpeedUnit();
4274 }
4275 }
4276
4277 m_pTrackRolloverWin->SetString(s);
4278
4279 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4280 LEG_ROLLOVER, win_size);
4281 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4282 m_pTrackRolloverWin->IsActive(true);
4283 b_need_refresh = true;
4284 showTrackRollover = true;
4285 break;
4286 }
4287 } else
4288 node = node->GetNext();
4289 }
4290 } else {
4291 // Is the cursor still in select radius, and not timed out?
4292 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4293 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4294 m_pRolloverTrackSeg))
4295 showTrackRollover = false;
4296 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4297 showTrackRollover = false;
4298 else
4299 showTrackRollover = true;
4300 }
4301
4302 // Similar for AIS target rollover window
4303 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4304 showTrackRollover = false;
4305
4306 // Similar for route rollover window
4307 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4308 showTrackRollover = false;
4309
4310 // TODO We onlt show tracks on primary canvas....
4311 // if(!IsPrimaryCanvas())
4312 // showTrackRollover = false;
4313
4314 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4315 !showTrackRollover) {
4316 m_pTrackRolloverWin->IsActive(false);
4317 m_pRolloverTrackSeg = NULL;
4318 m_pTrackRolloverWin->Destroy();
4319 m_pTrackRolloverWin = NULL;
4320 b_need_refresh = true;
4321 } else if (m_pTrackRolloverWin && showTrackRollover) {
4322 m_pTrackRolloverWin->IsActive(true);
4323 b_need_refresh = true;
4324 }
4325
4326 if (b_need_refresh) Refresh();
4327}
4328
4329void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4330 if ((GetShowENCLights() || m_bsectors_shown) &&
4331 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4332 extendedSectorLegs)) {
4333 if (!m_bsectors_shown) {
4334 ReloadVP(false);
4335 m_bsectors_shown = true;
4336 }
4337 } else {
4338 if (m_bsectors_shown) {
4339 ReloadVP(false);
4340 m_bsectors_shown = false;
4341 }
4342 }
4343
4344// This is here because GTK status window update is expensive..
4345// cairo using pango rebuilds the font every time so is very
4346// inefficient
4347// Anyway, only update the status bar when this timer expires
4348#if defined(__WXGTK__) || defined(__WXQT__)
4349 {
4350 // Check the absolute range of the cursor position
4351 // There could be a window wherein the chart geoereferencing is not
4352 // valid....
4353 double cursor_lat, cursor_lon;
4354 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4355
4356 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4357 while (cursor_lon < -180.) cursor_lon += 360.;
4358
4359 while (cursor_lon > 180.) cursor_lon -= 360.;
4360
4361 SetCursorStatus(cursor_lat, cursor_lon);
4362 }
4363 }
4364#endif
4365}
4366
4367void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4368 if (!parent_frame->m_pStatusBar) return;
4369
4370 wxString s1;
4371 s1 += _T(" ");
4372 s1 += toSDMM(1, cursor_lat);
4373 s1 += _T(" ");
4374 s1 += toSDMM(2, cursor_lon);
4375
4376 if (STAT_FIELD_CURSOR_LL >= 0)
4377 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4378
4379 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4380
4381 double brg, dist;
4382 wxString sm;
4383 wxString st;
4384 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4385 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4386 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4387
4388 wxString s = st + sm;
4389 s << FormatDistanceAdaptive(dist);
4390
4391 // CUSTOMIZATION - LIVE ETA OPTION
4392 // -------------------------------------------------------
4393 // Calculate an "live" ETA based on route starting from the current
4394 // position of the boat and goes to the cursor of the mouse.
4395 // In any case, an standard ETA will be calculated with a default speed
4396 // of the boat to give an estimation of the route (in particular if GPS
4397 // is off).
4398
4399 // Display only if option "live ETA" is selected in Settings > Display >
4400 // General.
4401 if (g_bShowLiveETA) {
4402 float realTimeETA;
4403 float boatSpeed;
4404 float boatSpeedDefault = g_defaultBoatSpeed;
4405
4406 // Calculate Estimate Time to Arrival (ETA) in minutes
4407 // Check before is value not closed to zero (it will make an very big
4408 // number...)
4409 if (!std::isnan(gSog)) {
4410 boatSpeed = gSog;
4411 if (boatSpeed < 0.5) {
4412 realTimeETA = 0;
4413 } else {
4414 realTimeETA = dist / boatSpeed * 60;
4415 }
4416 } else {
4417 realTimeETA = 0;
4418 }
4419
4420 // Add space after distance display
4421 s << " ";
4422 // Display ETA
4423 s << minutesToHoursDays(realTimeETA);
4424
4425 // In any case, display also an ETA with default speed at 6knts
4426
4427 s << " [@";
4428 s << wxString::Format(_T("%d"), (int)toUsrSpeed(boatSpeedDefault, -1));
4429 s << wxString::Format(_T("%s"), getUsrSpeedUnit(-1));
4430 s << " ";
4431 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4432 s << "]";
4433 }
4434 // END OF - LIVE ETA OPTION
4435
4436 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4437}
4438
4439// CUSTOMIZATION - FORMAT MINUTES
4440// -------------------------------------------------------
4441// New function to format minutes into a more readable format:
4442// * Hours + minutes, or
4443// * Days + hours.
4444wxString minutesToHoursDays(float timeInMinutes) {
4445 wxString s;
4446
4447 if (timeInMinutes == 0) {
4448 s << "--min";
4449 }
4450
4451 // Less than 60min, keep time in minutes
4452 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4453 s << wxString::Format(_T("%d"), (int)timeInMinutes);
4454 s << "min";
4455 }
4456
4457 // Between 1h and less than 24h, display time in hours, minutes
4458 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4459 int hours;
4460 int min;
4461 hours = (int)timeInMinutes / 60;
4462 min = (int)timeInMinutes % 60;
4463
4464 if (min == 0) {
4465 s << wxString::Format(_T("%d"), hours);
4466 s << "h";
4467 } else {
4468 s << wxString::Format(_T("%d"), hours);
4469 s << "h";
4470 s << wxString::Format(_T("%d"), min);
4471 s << "min";
4472 }
4473
4474 }
4475
4476 // More than 24h, display time in days, hours
4477 else if (timeInMinutes > 24 * 60) {
4478 int days;
4479 int hours;
4480 days = (int)(timeInMinutes / 60) / 24;
4481 hours = (int)(timeInMinutes / 60) % 24;
4482
4483 if (hours == 0) {
4484 s << wxString::Format(_T("%d"), days);
4485 s << "d";
4486 } else {
4487 s << wxString::Format(_T("%d"), days);
4488 s << "d";
4489 s << wxString::Format(_T("%d"), hours);
4490 s << "h";
4491 }
4492 }
4493
4494 return s;
4495}
4496
4497// END OF CUSTOMIZATION - FORMAT MINUTES
4498// Thanks open source code ;-)
4499// -------------------------------------------------------
4500
4501void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4502 double clat, clon;
4503 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4504 *lat = clat;
4505 *lon = clon;
4506}
4507
4508void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4509 wxPoint2DDouble *r) {
4510 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4511}
4512
4514 double rlon, wxPoint2DDouble *r) {
4515 // If the Current Chart is a raster chart, and the
4516 // requested lat/long is within the boundaries of the chart,
4517 // and the VP is not rotated,
4518 // then use the embedded BSB chart georeferencing algorithm
4519 // for greater accuracy
4520 // Additionally, use chart embedded georef if the projection is TMERC
4521 // i.e. NOT MERCATOR and NOT POLYCONIC
4522
4523 // If for some reason the chart rejects the request by returning an error,
4524 // then fall back to Viewport Projection estimate from canvas parameters
4525 if (!g_bopengl && m_singleChart &&
4526 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4527 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4528 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4529 (m_singleChart->GetChartProjectionType() !=
4530 PROJECTION_TRANSVERSE_MERCATOR) &&
4531 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4532 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4533 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4534 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4535 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4536 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4537 // Cur_BSB_Ch->GetCOVRTablenPoints
4538 // ( 0 ), rlon,
4539 // rlat );
4540 // bInside = true;
4541 // if ( bInside )
4542 if (Cur_BSB_Ch) {
4543 // This is a Raster chart....
4544 // If the VP is changing, the raster chart parameters may not yet be
4545 // setup So do that before accessing the chart's embedded
4546 // georeferencing
4547 Cur_BSB_Ch->SetVPRasterParms(vp);
4548 double rpixxd, rpixyd;
4549 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4550 r->m_x = rpixxd;
4551 r->m_y = rpixyd;
4552 return;
4553 }
4554 }
4555 }
4556
4557 // if needed, use the VPoint scaling estimator,
4558 *r = vp.GetDoublePixFromLL(rlat, rlon);
4559}
4560
4561// This routine might be deleted and all of the rendering improved
4562// to have floating point accuracy
4563bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4564 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4565}
4566
4567bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4568 wxPoint *r) {
4569 wxPoint2DDouble p;
4570 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4571
4572 // some projections give nan values when invisible values (other side of
4573 // world) are requested we should stop using integer coordinates or return
4574 // false here (and test it everywhere)
4575 if (std::isnan(p.m_x)) {
4576 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4577 return false;
4578 }
4579
4580 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4581 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4582 else
4583 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4584
4585 return true;
4586}
4587
4588void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4589 double &lon) {
4590 // If the Current Chart is a raster chart, and the
4591 // requested x,y is within the boundaries of the chart,
4592 // and the VP is not rotated,
4593 // then use the embedded BSB chart georeferencing algorithm
4594 // for greater accuracy
4595 // Additionally, use chart embedded georef if the projection is TMERC
4596 // i.e. NOT MERCATOR and NOT POLYCONIC
4597
4598 // If for some reason the chart rejects the request by returning an error,
4599 // then fall back to Viewport Projection estimate from canvas parameters
4600 bool bUseVP = true;
4601
4602 if (!g_bopengl && m_singleChart &&
4603 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4604 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4605 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4606 (m_singleChart->GetChartProjectionType() !=
4607 PROJECTION_TRANSVERSE_MERCATOR) &&
4608 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4609 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4610 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4611 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4612
4613 // TODO maybe need iterative process to validate bInside
4614 // first pass is mercator, then check chart boundaries
4615
4616 if (Cur_BSB_Ch) {
4617 // This is a Raster chart....
4618 // If the VP is changing, the raster chart parameters may not yet be
4619 // setup So do that before accessing the chart's embedded
4620 // georeferencing
4621 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4622
4623 double slat, slon;
4624 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4625 lat = slat;
4626
4627 if (slon < -180.)
4628 slon += 360.;
4629 else if (slon > 180.)
4630 slon -= 360.;
4631
4632 lon = slon;
4633 bUseVP = false;
4634 }
4635 }
4636 }
4637
4638 // if needed, use the VPoint scaling estimator
4639 if (bUseVP) {
4640 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4641 }
4642}
4643
4645 DoZoomCanvas(factor, false);
4646 extendedSectorLegs.clear();
4647}
4648
4649void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4650 bool stoptimer) {
4651 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4652
4653 if (g_bsmoothpanzoom) {
4654 if (StartTimedMovement(stoptimer)) {
4655 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4656 m_zoom_factor = factor;
4657 }
4658
4659 m_zoom_target = VPoint.chart_scale / factor;
4660 } else {
4661 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4662
4663 DoZoomCanvas(factor, can_zoom_to_cursor);
4664 }
4665
4666 extendedSectorLegs.clear();
4667}
4668
4669void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4670 // possible on startup
4671 if (!ChartData) return;
4672 if (!m_pCurrentStack) return;
4673
4674 /* TODO: queue the quilted loading code to a background thread
4675 so yield is never called from here, and also rendering is not delayed */
4676
4677 // Cannot allow Yield() re-entrancy here
4678 if (m_bzooming) return;
4679 m_bzooming = true;
4680
4681 double old_ppm = GetVP().view_scale_ppm;
4682
4683 // Capture current cursor position for zoom to cursor
4684 double zlat = m_cursor_lat;
4685 double zlon = m_cursor_lon;
4686
4687 double proposed_scale_onscreen =
4688 GetVP().chart_scale /
4689 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4690 bool b_do_zoom = false;
4691
4692 if (factor > 1) {
4693 b_do_zoom = true;
4694
4695 // double zoom_factor = factor;
4696
4697 ChartBase *pc = NULL;
4698
4699 if (!VPoint.b_quilt) {
4700 pc = m_singleChart;
4701 } else {
4702 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4703 if (new_db_index >= 0)
4704 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4705 else { // for whatever reason, no reference chart is known
4706 // Choose the smallest scale chart on the current stack
4707 // and then adjust for scale range
4708 int current_ref_stack_index = -1;
4709 if (m_pCurrentStack->nEntry) {
4710 int trial_index =
4711 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4712 m_pQuilt->SetReferenceChart(trial_index);
4713 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4714 if (new_db_index >= 0)
4715 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4716 }
4717 }
4718
4719 if (m_pCurrentStack)
4720 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4721 new_db_index); // highlite the correct bar entry
4722 }
4723
4724 if (pc) {
4725 // double target_scale_ppm = GetVPScale() * zoom_factor;
4726 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4727 // target_scale_ppm;
4728
4729 // Query the chart to determine the appropriate zoom range
4730 double min_allowed_scale =
4731 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4732
4733 if (proposed_scale_onscreen < min_allowed_scale) {
4734 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4735 m_zoom_factor = 1; /* stop zooming */
4736 b_do_zoom = false;
4737 } else
4738 proposed_scale_onscreen = min_allowed_scale;
4739 }
4740
4741 } else {
4742 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4743 }
4744
4745 } else if (factor < 1) {
4746 b_do_zoom = true;
4747
4748 ChartBase *pc = NULL;
4749
4750 bool b_smallest = false;
4751
4752 if (!VPoint.b_quilt) { // not quilted
4753 pc = m_singleChart;
4754
4755 if (pc) {
4756 // If m_singleChart is not on the screen, unbound the zoomout
4757 LLBBox viewbox = VPoint.GetBBox();
4758 // BoundingBox chart_box;
4759 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4760 double max_allowed_scale;
4761
4762 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4763
4764 // We can allow essentially unbounded zoomout in single chart mode
4765 // if( ChartData->GetDBBoundingBox( current_index,
4766 // &chart_box ) &&
4767 // !viewbox.IntersectOut( chart_box ) )
4768 // // Clamp the minimum scale zoom-out to the value
4769 // specified by the chart max_allowed_scale =
4770 // wxMin(max_allowed_scale, 4.0 *
4771 // pc->GetNormalScaleMax(
4772 // GetCanvasScaleFactor(),
4773 // GetCanvasWidth() ) );
4774 if (proposed_scale_onscreen > max_allowed_scale) {
4775 m_zoom_factor = 1; /* stop zooming */
4776 proposed_scale_onscreen = max_allowed_scale;
4777 }
4778 }
4779
4780 } else {
4781 int new_db_index = m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4782 if (new_db_index >= 0)
4783 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4784
4785 if (m_pCurrentStack)
4786 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4787 new_db_index); // highlite the correct bar entry
4788
4789 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4790
4791 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4792 proposed_scale_onscreen =
4793 wxMin(proposed_scale_onscreen,
4794 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4795 }
4796
4797 // set a minimum scale
4798 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4799 m_absolute_min_scale_ppm)
4800 b_do_zoom = false;
4801 }
4802
4803 double new_scale =
4804 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4805
4806 if (b_do_zoom) {
4807 // Disable ZTC if lookahead is ON, and currently b_follow is active
4808 bool b_allow_ztc = true;
4809 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4810 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4811 if (m_bLookAhead) {
4812 double brg, distance;
4813 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4814 &distance);
4815 dir_to_shift = brg;
4816 meters_to_shift = distance * 1852;
4817 }
4818 // Arrange to combine the zoom and pan into one operation for smoother
4819 // appearance
4820 SetVPScale(new_scale, false); // adjust, but deferred refresh
4821 wxPoint r;
4822 GetCanvasPointPix(zlat, zlon, &r);
4823 // this will emit the Refresh()
4824 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4825 } else {
4826 SetVPScale(new_scale);
4827 if (m_bFollow) DoCanvasUpdate();
4828 }
4829 }
4830
4831 m_bzooming = false;
4832}
4833int rot;
4834void ChartCanvas::RotateCanvas(double dir) {
4835 // SetUpMode(NORTH_UP_MODE);
4836
4837 if (g_bsmoothpanzoom) {
4838 if (StartTimedMovement()) {
4839 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4840 m_rotation_speed = dir * 60;
4841 }
4842 } else {
4843 double speed = dir * 10;
4844 if (m_modkeys == wxMOD_ALT) speed /= 20;
4845 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4846 }
4847}
4848
4849void ChartCanvas::DoRotateCanvas(double rotation) {
4850 while (rotation < 0) rotation += 2 * PI;
4851 while (rotation > 2 * PI) rotation -= 2 * PI;
4852
4853 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4854
4855 SetVPRotation(rotation);
4856 parent_frame->UpdateRotationState(VPoint.rotation);
4857}
4858
4859void ChartCanvas::DoTiltCanvas(double tilt) {
4860 while (tilt < 0) tilt = 0;
4861 while (tilt > .95) tilt = .95;
4862
4863 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4864
4865 VPoint.tilt = tilt;
4866 Refresh(false);
4867}
4868
4869void ChartCanvas::TogglebFollow(void) {
4870 if (!m_bFollow)
4871 SetbFollow();
4872 else
4873 ClearbFollow();
4874}
4875
4876void ChartCanvas::ClearbFollow(void) {
4877 m_bFollow = false; // update the follow flag
4878
4879 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4880
4881 UpdateFollowButtonState();
4882
4883 DoCanvasUpdate();
4884 ReloadVP();
4885 parent_frame->SetChartUpdatePeriod();
4886}
4887
4888void ChartCanvas::SetbFollow(void) {
4889 // Is the OWNSHIP on-screen?
4890 // If not, then reset the OWNSHIP offset to 0 (center screen)
4891 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4892 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4893 m_OSoffsetx = 0;
4894 m_OSoffsety = 0;
4895 }
4896
4897 // Apply the present b_follow offset values to ship position
4898 wxPoint2DDouble p;
4899 GetDoubleCanvasPointPix(gLat, gLon, &p);
4900 p.m_x += m_OSoffsetx;
4901 p.m_y -= m_OSoffsety;
4902
4903 // compute the target center screen lat/lon
4904 double dlat, dlon;
4905 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4906
4907 JumpToPosition(dlat, dlon, GetVPScale());
4908 m_bFollow = true;
4909
4910 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4911 UpdateFollowButtonState();
4912
4913 if (!g_bSmoothRecenter) {
4914 DoCanvasUpdate();
4915 ReloadVP();
4916 }
4917 parent_frame->SetChartUpdatePeriod();
4918}
4919
4920void ChartCanvas::UpdateFollowButtonState(void) {
4921 if (m_muiBar) {
4922 if (!m_bFollow)
4923 m_muiBar->SetFollowButtonState(0);
4924 else {
4925 if (m_bLookAhead)
4926 m_muiBar->SetFollowButtonState(2);
4927 else
4928 m_muiBar->SetFollowButtonState(1);
4929 }
4930 }
4931
4932#ifdef __ANDROID__
4933 if (!m_bFollow)
4934 androidSetFollowTool(0);
4935 else {
4936 if (m_bLookAhead)
4937 androidSetFollowTool(2);
4938 else
4939 androidSetFollowTool(1);
4940 }
4941#endif
4942}
4943
4944void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4945 if (g_bSmoothRecenter && !m_routeState) {
4946 if (StartSmoothJump(lat, lon, scale_ppm))
4947 return;
4948 else {
4949 // move closer to the target destination, and try again
4950 double gcDist, gcBearingEnd;
4951 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4952 &gcBearingEnd);
4953 gcBearingEnd += 180;
4954 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4955 GetCanvasWidth() / GetVPScale(); // meters
4956 double lon_offset =
4957 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4958 double new_lat = lat + (lat_offset / (1852 * 60));
4959 double new_lon = lon + (lon_offset / (1852 * 60));
4960 SetViewPoint(new_lat, new_lon);
4961 ReloadVP();
4962 StartSmoothJump(lat, lon, scale_ppm);
4963 return;
4964 }
4965 }
4966
4967 if (lon > 180.0) lon -= 360.0;
4968 m_vLat = lat;
4969 m_vLon = lon;
4970 StopMovement();
4971 m_bFollow = false;
4972
4973 if (!GetQuiltMode()) {
4974 double skew = 0;
4975 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4976 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4977 } else {
4978 if (scale_ppm != GetVPScale()) {
4979 // XXX should be done in SetViewPoint
4980 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4981 AdjustQuiltRefChart();
4982 }
4983 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4984 }
4985
4986 ReloadVP();
4987
4988 UpdateFollowButtonState();
4989
4990 // TODO
4991 // if( g_pi_manager ) {
4992 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4993 // }
4994}
4995
4996bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
4997 // Check distance to jump, in pixels at current chart scale
4998 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
4999 // width.
5000 double gcDist;
5001 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5002 double distance_pixels = gcDist * GetVPScale();
5003 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5004 // Jump is too far, try again
5005 return false;
5006 }
5007
5008 // Save where we're coming from
5009 m_startLat = m_vLat;
5010 m_startLon = m_vLon;
5011 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5012
5013 // Save where we want to end up
5014 m_endLat = lat;
5015 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5016 m_endScale = scale_ppm;
5017
5018 // Setup timing
5019 m_animationDuration = 600; // ms
5020 m_animationStart = wxGetLocalTimeMillis();
5021
5022 // Stop any previous movement, ensure no conflicts
5023 StopMovement();
5024 m_bFollow = false;
5025
5026 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5027 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5028 m_animationActive = true;
5029
5030 return true;
5031}
5032
5033void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5034 // Calculate time fraction from 0..1
5035 wxLongLong now = wxGetLocalTimeMillis();
5036 double elapsed = (now - m_animationStart).ToDouble();
5037 double t = elapsed / m_animationDuration.ToDouble();
5038 if (t > 1.0) t = 1.0;
5039
5040 // Ease function for smoother movement
5041 double e = easeOutCubic(t);
5042
5043 // Interpolate lat/lon/scale
5044 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5045 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5046 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5047
5048 // Update viewpoint
5049 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5050 // portion)
5051 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5052 ReloadVP();
5053
5054 // If we reached the end, stop the timer and finalize
5055 if (t >= 1.0) {
5056 m_easeTimer.Stop();
5057 m_animationActive = false;
5058 UpdateFollowButtonState();
5059 DoCanvasUpdate();
5060 ReloadVP();
5061 }
5062}
5063
5064bool ChartCanvas::PanCanvas(double dx, double dy) {
5065 if (!ChartData) return false;
5066
5067 if (g_btouch) {
5068 // Stop bfollow state, without a refresh
5069 m_bFollow = false; // update the follow flag
5070 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
5071 UpdateFollowButtonState();
5072 // Clear the bfollow offset
5073 m_OSoffsetx = 0;
5074 m_OSoffsety = 0;
5075 }
5076
5077 extendedSectorLegs.clear();
5078
5079 // double clat = VPoint.clat, clon = VPoint.clon;
5080 double dlat, dlon;
5081 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5082
5083 int iters = 0;
5084 for (;;) {
5085 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5086
5087 if (iters++ > 5) return false;
5088 if (!std::isnan(dlat)) break;
5089
5090 dx *= .5, dy *= .5;
5091 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5092 }
5093
5094 // avoid overshooting the poles
5095 if (dlat > 90)
5096 dlat = 90;
5097 else if (dlat < -90)
5098 dlat = -90;
5099
5100 if (dlon > 360.) dlon -= 360.;
5101 if (dlon < -360.) dlon += 360.;
5102
5103 // This should not really be necessary, but round-trip georef on some
5104 // charts is not perfect, So we can get creep on repeated unidimensional
5105 // pans, and corrupt chart cacheing.......
5106
5107 // But this only works on north-up projections
5108 // TODO: can we remove this now?
5109 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5110 // .001 ) ) {
5111 //
5112 // if( dx == 0 ) dlon = clon;
5113 // if( dy == 0 ) dlat = clat;
5114 // }
5115
5116 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5117
5118 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5119
5120 if (VPoint.b_quilt) {
5121 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5122 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5123 // Tweak the scale slightly for a new ref chart
5124 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5125 if (pc) {
5126 double tweak_scale_ppm =
5127 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5128 SetVPScale(tweak_scale_ppm);
5129 }
5130 }
5131
5132 if (new_ref_dbIndex == -1) {
5133 // for whatever reason, no reference chart is known
5134 // Probably panned out of the coverage region
5135 // If any charts are anywhere on-screen, choose the smallest
5136 // scale chart on the screen to be a new reference chart.
5137 int trial_index = -1;
5138 if (m_pCurrentStack->nEntry) {
5139 int trial_index =
5140 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5141 }
5142
5143 if (trial_index < 0) {
5144 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5145 if (full_screen_array.size())
5146 trial_index = full_screen_array[full_screen_array.size() - 1];
5147 }
5148
5149 if (trial_index >= 0) {
5150 m_pQuilt->SetReferenceChart(trial_index);
5151 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5152 VPoint.rotation);
5153 ReloadVP();
5154 }
5155 }
5156 }
5157
5158 // Turn off bFollow only if the ownship has left the screen
5159 if (m_bFollow) {
5160 double offx, offy;
5161 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5162
5163 double offset_angle = atan2(offy, offx);
5164 double offset_distance = sqrt((offy * offy) + (offx * offx));
5165 double chart_angle = GetVPRotation();
5166 double target_angle = chart_angle - offset_angle;
5167 double d_east_mod = offset_distance * cos(target_angle);
5168 double d_north_mod = offset_distance * sin(target_angle);
5169
5170 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5171 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5172
5173 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5174 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5175 m_bFollow = false; // update the follow flag
5176 UpdateFollowButtonState();
5177 }
5178 }
5179
5180 Refresh(false);
5181
5182 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5183
5184 return true;
5185}
5186
5187void ChartCanvas::ReloadVP(bool b_adjust) {
5188 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5189
5190 LoadVP(VPoint, b_adjust);
5191}
5192
5193void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5194#ifdef ocpnUSE_GL
5195 if (g_bopengl && m_glcc) {
5196 m_glcc->Invalidate();
5197 if (m_glcc->GetSize() != GetSize()) {
5198 m_glcc->SetSize(GetSize());
5199 }
5200 } else
5201#endif
5202 {
5203 m_cache_vp.Invalidate();
5204 m_bm_cache_vp.Invalidate();
5205 }
5206
5207 VPoint.Invalidate();
5208
5209 if (m_pQuilt) m_pQuilt->Invalidate();
5210
5211 // Make sure that the Selected Group is sensible...
5212 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5213 // m_groupIndex = 0;
5214 // if( !CheckGroup( m_groupIndex ) )
5215 // m_groupIndex = 0;
5216
5217 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5218 vp.m_projection_type, b_adjust);
5219}
5220
5221void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5222 m_pQuilt->SetReferenceChart(dbIndex);
5223 VPoint.Invalidate();
5224 m_pQuilt->Invalidate();
5225}
5226
5227double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5228 if (m_pQuilt)
5229 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5230 else
5231 return vp.view_scale_ppm;
5232}
5233
5234// Verify and adjust the current reference chart,
5235// so that it will not lead to excessive overzoom or underzoom onscreen
5236int ChartCanvas::AdjustQuiltRefChart() {
5237 int ret = -1;
5238 if (m_pQuilt) {
5239 wxASSERT(ChartData);
5240 ChartBase *pc =
5241 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5242 if (pc) {
5243 double min_ref_scale =
5244 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5245 double max_ref_scale =
5246 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5247
5248 if (VPoint.chart_scale < min_ref_scale) {
5249 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5250 } else if (VPoint.chart_scale > max_ref_scale) {
5251 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5252 } else {
5253 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5254
5255 int ref_family = pc->GetChartFamily();
5256
5257 if (!brender_ok) {
5258 unsigned int target_stack_index = 0;
5259 int target_stack_index_check =
5260 m_pQuilt->GetExtendedStackIndexArray()
5261 [m_pQuilt->GetRefChartdbIndex()]; // Lookup
5262
5263 if (wxNOT_FOUND != target_stack_index_check)
5264 target_stack_index = target_stack_index_check;
5265
5266 int extended_array_count =
5267 m_pQuilt->GetExtendedStackIndexArray().size();
5268 while ((!brender_ok) &&
5269 ((int)target_stack_index < (extended_array_count - 1))) {
5270 target_stack_index++;
5271 int test_db_index =
5272 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5273
5274 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5275 IsChartQuiltableRef(test_db_index)) {
5276 // open the target, and check the min_scale
5277 ChartBase *ptest_chart =
5278 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5279 if (ptest_chart) {
5280 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5281 }
5282 }
5283 }
5284
5285 if (brender_ok) { // found a better reference chart
5286 int new_db_index =
5287 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5288 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5289 IsChartQuiltableRef(new_db_index)) {
5290 m_pQuilt->SetReferenceChart(new_db_index);
5291 ret = new_db_index;
5292 } else
5293 ret = m_pQuilt->GetRefChartdbIndex();
5294 } else
5295 ret = m_pQuilt->GetRefChartdbIndex();
5296
5297 } else
5298 ret = m_pQuilt->GetRefChartdbIndex();
5299 }
5300 } else
5301 ret = -1;
5302 }
5303
5304 return ret;
5305}
5306
5307void ChartCanvas::UpdateCanvasOnGroupChange(void) {
5308 delete m_pCurrentStack;
5309 m_pCurrentStack = new ChartStack;
5310 wxASSERT(ChartData);
5311 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5312 m_groupIndex);
5313
5314 if (m_pQuilt) {
5315 m_pQuilt->Compose(VPoint);
5316 SetFocus();
5317 }
5318}
5319
5320bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5321 double latNE, double lonNE) {
5322 // Center Point
5323 double latc = (latSW + latNE) / 2.0;
5324 double lonc = (lonSW + lonNE) / 2.0;
5325
5326 // Get scale in ppm (latitude)
5327 double ne_easting, ne_northing;
5328 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5329
5330 double sw_easting, sw_northing;
5331 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5332
5333 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5334
5335 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5336}
5337
5338bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5339 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5340 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5341}
5342
5343bool ChartCanvas::SetVPProjection(int projection) {
5344 if (!g_bopengl) // alternative projections require opengl
5345 return false;
5346
5347 // the view scale varies depending on geographic location and projection
5348 // rescale to keep the relative scale on the screen the same
5349 double prev_true_scale_ppm = m_true_scale_ppm;
5350 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5351 VPoint.skew, VPoint.rotation, projection) &&
5352 SetVPScale(wxMax(
5353 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5354 m_absolute_min_scale_ppm));
5355}
5356
5357bool ChartCanvas::SetViewPoint(double lat, double lon) {
5358 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5359 VPoint.rotation);
5360}
5361
5362bool ChartCanvas::SetVPRotation(double angle) {
5363 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5364 VPoint.skew, angle);
5365}
5366bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5367 double skew, double rotation, int projection,
5368 bool b_adjust, bool b_refresh) {
5369 bool b_ret = false;
5370 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5371 skew -= 2 * PI;
5372 // Any sensible change?
5373 if (VPoint.IsValid()) {
5374 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5375 (fabs(VPoint.skew - skew) < 1e-9) &&
5376 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5377 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5378 (VPoint.m_projection_type == projection ||
5379 projection == PROJECTION_UNKNOWN))
5380 return false;
5381 }
5382 if (VPoint.m_projection_type != projection)
5383 VPoint.InvalidateTransformCache(); // invalidate
5384
5385 // Take a local copy of the last viewport
5386 ViewPort last_vp = VPoint;
5387
5388 VPoint.skew = skew;
5389 VPoint.clat = lat;
5390 VPoint.clon = lon;
5391 VPoint.rotation = rotation;
5392 VPoint.view_scale_ppm = scale_ppm;
5393 if (projection != PROJECTION_UNKNOWN)
5394 VPoint.SetProjectionType(projection);
5395 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5396 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5397
5398 // don't allow latitude above 88 for mercator (90 is infinity)
5399 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5400 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5401 if (VPoint.clat > 89.5)
5402 VPoint.clat = 89.5;
5403 else if (VPoint.clat < -89.5)
5404 VPoint.clat = -89.5;
5405 }
5406
5407 // don't zoom out too far for transverse mercator polyconic until we resolve
5408 // issues
5409 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5410 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5411 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5412
5413 // SetVPRotation(rotation);
5414
5415 if (!g_bopengl) // tilt is not possible without opengl
5416 VPoint.tilt = 0;
5417
5418 if ((VPoint.pix_width <= 0) ||
5419 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5420 return false;
5421
5422 bool bwasValid = VPoint.IsValid();
5423 VPoint.Validate(); // Mark this ViewPoint as OK
5424
5425 // Has the Viewport scale changed? If so, invalidate the vp
5426 if (last_vp.view_scale_ppm != scale_ppm) {
5427 m_cache_vp.Invalidate();
5428 InvalidateGL();
5429 }
5430
5431 // A preliminary value, may be tweaked below
5432 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5433
5434 // recompute cursor position
5435 // and send to interested plugins if the mouse is actually in this window
5436 int mouseX = mouse_x;
5437 int mouseY = mouse_y;
5438 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5439 (mouseY < VPoint.pix_height)) {
5440 double lat, lon;
5441 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5442 m_cursor_lat = lat;
5443 m_cursor_lon = lon;
5444 SendCursorLatLonToAllPlugIns(lat, lon);
5445 }
5446
5447 if (!VPoint.b_quilt && m_singleChart) {
5448 VPoint.SetBoxes();
5449
5450 // Allow the chart to adjust the new ViewPort for performance optimization
5451 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5452 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5453
5454 // If there is a sensible change in the chart render, refresh the whole
5455 // screen
5456 if ((!m_cache_vp.IsValid()) ||
5457 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5458 Refresh(false);
5459 b_ret = true;
5460 } else {
5461 wxPoint cp_last, cp_this;
5462 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5463 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5464
5465 if (cp_last != cp_this) {
5466 Refresh(false);
5467 b_ret = true;
5468 }
5469 }
5470 // Create the stack
5471 if (m_pCurrentStack) {
5472 assert(ChartData != 0);
5473 int current_db_index;
5474 current_db_index =
5475 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5476
5477 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5478 m_groupIndex);
5479 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5480 }
5481
5482 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5483 }
5484
5485 // Handle the quilted case
5486 if (VPoint.b_quilt) {
5487 if (last_vp.view_scale_ppm != scale_ppm)
5488 m_pQuilt->InvalidateAllQuiltPatchs();
5489
5490 // Create the quilt
5491 if (ChartData /*&& ChartData->IsValid()*/) {
5492 if (!m_pCurrentStack) return false;
5493
5494 int current_db_index;
5495 current_db_index =
5496 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5497
5498 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5499 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5500
5501 // Check to see if the current quilt reference chart is in the new stack
5502 int current_ref_stack_index = -1;
5503 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5504 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5505 current_ref_stack_index = i;
5506 }
5507
5508 if (g_bFullScreenQuilt) {
5509 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5510 }
5511
5512 // We might need a new Reference Chart
5513 bool b_needNewRef = false;
5514
5515 // If the new stack does not contain the current ref chart....
5516 if ((-1 == current_ref_stack_index) &&
5517 (m_pQuilt->GetRefChartdbIndex() >= 0))
5518 b_needNewRef = true;
5519
5520 // Would the current Ref Chart be excessively underzoomed?
5521 // We need to check this here to be sure, since we cannot know where the
5522 // reference chart was assigned. For instance, the reference chart may
5523 // have been selected from the config file, or from a long jump with a
5524 // chart family switch implicit. Anyway, we check to be sure....
5525 bool renderable = true;
5526 ChartBase *referenceChart =
5527 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5528 if (referenceChart) {
5529 double chartMaxScale = referenceChart->GetNormalScaleMax(
5530 GetCanvasScaleFactor(), GetCanvasWidth());
5531 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5532 }
5533 if (!renderable) b_needNewRef = true;
5534
5535 // Need new refchart?
5536 if (b_needNewRef) {
5537 const ChartTableEntry &cte_ref =
5538 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5539 int target_scale = cte_ref.GetScale();
5540 int target_type = cte_ref.GetChartType();
5541 int candidate_stack_index;
5542
5543 // reset the ref chart in a way that does not lead to excessive
5544 // underzoom, for performance reasons Try to find a chart that is the
5545 // same type, and has a scale of just smaller than the current ref
5546 // chart
5547
5548 candidate_stack_index = 0;
5549 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5550 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5551 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5552 int candidate_scale = cte_candidate.GetScale();
5553 int candidate_type = cte_candidate.GetChartType();
5554
5555 if ((candidate_scale >= target_scale) &&
5556 (candidate_type == target_type)) {
5557 bool renderable = true;
5558 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5559 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5560 if (tentative_referenceChart) {
5561 double chartMaxScale =
5562 tentative_referenceChart->GetNormalScaleMax(
5563 GetCanvasScaleFactor(), GetCanvasWidth());
5564 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5565 }
5566
5567 if (renderable) break;
5568 }
5569
5570 candidate_stack_index++;
5571 }
5572
5573 // If that did not work, look for a chart of just larger scale and
5574 // same type
5575 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5576 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5577 while (candidate_stack_index >= 0) {
5578 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5579 if (idx >= 0) {
5580 const ChartTableEntry &cte_candidate =
5581 ChartData->GetChartTableEntry(idx);
5582 int candidate_scale = cte_candidate.GetScale();
5583 int candidate_type = cte_candidate.GetChartType();
5584
5585 if ((candidate_scale <= target_scale) &&
5586 (candidate_type == target_type))
5587 break;
5588 }
5589 candidate_stack_index--;
5590 }
5591 }
5592
5593 // and if that did not work, chose stack entry 0
5594 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5595 (candidate_stack_index < 0))
5596 candidate_stack_index = 0;
5597
5598 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5599
5600 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5601 }
5602
5603 if (!g_bopengl) {
5604 // Preset the VPoint projection type to match what the quilt projection
5605 // type will be
5606 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5607
5608 // Always keep the default Mercator projection if the reference chart is
5609 // not in the PatchList or the scale is too small for it to render.
5610
5611 bool renderable = true;
5612 ChartBase *referenceChart =
5613 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5614 if (referenceChart) {
5615 double chartMaxScale = referenceChart->GetNormalScaleMax(
5616 GetCanvasScaleFactor(), GetCanvasWidth());
5617 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5618 proj = ChartData->GetDBChartProj(ref_db_index);
5619 } else
5620 proj = PROJECTION_MERCATOR;
5621
5622 VPoint.b_MercatorProjectionOverride =
5623 (m_pQuilt->GetnCharts() == 0 || !renderable);
5624
5625 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5626
5627 VPoint.SetProjectionType(proj);
5628 }
5629
5630 VPoint.SetBoxes();
5631
5632 // If this quilt will be a perceptible delta from the existing quilt,
5633 // then refresh the entire screen
5634 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5635 // Allow the quilt to adjust the new ViewPort for performance
5636 // optimization This will normally be only a fractional (i.e.
5637 // sub-pixel) adjustment...
5638 if (b_adjust) {
5639 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5640 }
5641
5642 // ChartData->ClearCacheInUseFlags();
5643 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5644
5645 // wxStopWatch sw;
5646
5647#ifdef __ANDROID__
5648 // This is an optimization for panning on touch screen systems.
5649 // The quilt composition is deferred until the OnPaint() message gets
5650 // finally removed and processed from the message queue.
5651 // Takes advantage of the fact that touch-screen pan gestures are
5652 // usually short in distance,
5653 // so not requiring a full quilt rebuild until the pan gesture is
5654 // complete.
5655 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5656 // qDebug() << "Force compose";
5657 m_pQuilt->Compose(VPoint);
5658 } else {
5659 m_pQuilt->Invalidate();
5660 }
5661#else
5662 m_pQuilt->Compose(VPoint);
5663#endif
5664
5665 // printf("comp time %ld\n", sw.Time());
5666
5667 // If the extended chart stack has changed, invalidate any cached
5668 // render bitmap
5669 // if(m_pQuilt->GetXStackHash() != hash1) {
5670 // m_bm_cache_vp.Invalidate();
5671 // InvalidateGL();
5672 // }
5673
5674 ChartData->PurgeCacheUnusedCharts(0.7);
5675
5676 if (b_refresh) Refresh(false);
5677
5678 b_ret = true;
5679 }
5680 }
5681
5682 VPoint.skew = 0.; // Quilting supports 0 Skew
5683 } else if (!g_bopengl) {
5684 OcpnProjType projection = PROJECTION_UNKNOWN;
5685 if (m_singleChart) // viewport projection must match chart projection
5686 // without opengl
5687 projection = m_singleChart->GetChartProjectionType();
5688 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5689 VPoint.SetProjectionType(projection);
5690 }
5691
5692 // Has the Viewport projection changed? If so, invalidate the vp
5693 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5694 m_cache_vp.Invalidate();
5695 InvalidateGL();
5696 }
5697
5698 UpdateCanvasControlBar(); // Refresh the Piano
5699
5700 VPoint.chart_scale = 1.0; // fallback default value
5701
5702 /*if (!VPoint.GetBBox().GetValid())*/ VPoint.SetBoxes();
5703
5704 if (VPoint.GetBBox().GetValid()) {
5705 // Update the viewpoint reference scale
5706 if (m_singleChart)
5707 VPoint.ref_scale = m_singleChart->GetNativeScale();
5708 else {
5709#ifdef __ANDROID__
5710 // This is an optimization for panning on touch screen systems.
5711 // See above.
5712 // Quilt might not be fully composed at this point, so for cm93
5713 // the reference scale may not be known.
5714 // In this case, do not update the VP ref_scale.
5715 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5716 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5717 }
5718#else
5719 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5720#endif
5721 }
5722
5723 // Calculate the on-screen displayed actual scale
5724 // by a simple traverse northward from the center point
5725 // of roughly one eighth of the canvas height
5726 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5727
5728 double delta_check =
5729 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5730 delta_check /= 8.;
5731
5732 double check_point = wxMin(89., VPoint.clat);
5733
5734 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5735
5736 double rhumbDist;
5737 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5738 VPoint.clon, 0, &rhumbDist);
5739
5740 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5741 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5742 // Calculate the distance between r1 and r in physical pixels.
5743 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5744 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5745
5746 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5747
5748 // A fall back in case of very high zoom-out, giving delta_y == 0
5749 // which can probably only happen with vector charts
5750 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5751
5752 // Another fallback, for highly zoomed out charts
5753 // This adjustment makes the displayed TrueScale correspond to the
5754 // same algorithm used to calculate the chart zoom-out limit for
5755 // ChartDummy.
5756 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5757
5758 if (m_true_scale_ppm)
5759 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5760 else
5761 VPoint.chart_scale = 1.0;
5762
5763 // Create a nice renderable string
5764 double round_factor = 1000.;
5765 if (VPoint.chart_scale <= 1000.)
5766 round_factor = 10.;
5767 else if (VPoint.chart_scale <= 10000.)
5768 round_factor = 100.;
5769 else if (VPoint.chart_scale <= 100000.)
5770 round_factor = 1000.;
5771
5772 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5773 double retina_coef = 1;
5774#ifdef ocpnUSE_GL
5775#ifdef __WXOSX__
5776 if (g_bopengl) {
5777 retina_coef = GetContentScaleFactor();
5778 }
5779#endif
5780#endif
5781
5782 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5783 // rounded to the nearest 10, 100 or 1000.
5784 //
5785 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5786 // true_scale_display. That does not make sense. The chart scale should be
5787 // the same as the true scale within the limits of the rounding factor.
5788 double true_scale_display =
5789 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5790 wxString text;
5791
5792 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5793
5794 if (m_displayed_scale_factor > 10.0)
5795 text.Printf(_T("%s %4.0f (%1.0fx)"), _("Scale"), true_scale_display,
5796 m_displayed_scale_factor);
5797 else if (m_displayed_scale_factor > 1.0)
5798 text.Printf(_T("%s %4.0f (%1.1fx)"), _("Scale"), true_scale_display,
5799 m_displayed_scale_factor);
5800 else if (m_displayed_scale_factor > 0.1) {
5801 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5802 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5803 } else if (m_displayed_scale_factor > 0.01) {
5804 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5805 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5806 } else {
5807 text.Printf(
5808 _T("%s %4.0f (---)"), _("Scale"),
5809 true_scale_display); // Generally, no chart, so no chart scale factor
5810 }
5811
5812 m_scaleValue = true_scale_display;
5813 m_scaleText = text;
5814 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5815
5816 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5817 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5818 // Check to see if the text will fit in the StatusBar field...
5819 bool b_noshow = false;
5820 {
5821 int w = 0;
5822 int h;
5823 wxClientDC dc(parent_frame->GetStatusBar());
5824 if (dc.IsOk()) {
5825 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5826 dc.SetFont(*templateFont);
5827 dc.GetTextExtent(text, &w, &h);
5828
5829 // If text is too long for the allocated field, try to reduce the text
5830 // string a bit.
5831 wxRect rect;
5832 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5833 if (w && w > rect.width) {
5834 text.Printf(_T("%s (%1.1fx)"), _("Scale"),
5835 m_displayed_scale_factor);
5836 }
5837
5838 // Test again...if too big still, then give it up.
5839 dc.GetTextExtent(text, &w, &h);
5840
5841 if (w && w > rect.width) {
5842 b_noshow = true;
5843 }
5844 }
5845 }
5846
5847 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5848 }
5849 }
5850
5851 // Maintain member vLat/vLon
5852 m_vLat = VPoint.clat;
5853 m_vLon = VPoint.clon;
5854
5855 return b_ret;
5856}
5857
5858// Static Icon definitions for some symbols requiring
5859// scaling/rotation/translation Very specific wxDC draw commands are
5860// necessary to properly render these icons...See the code in
5861// ShipDraw()
5862
5863// This icon was adapted and scaled from the S52 Presentation Library
5864// version 3_03.
5865// Symbol VECGND02
5866
5867static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5868
5869// This ownship icon was adapted and scaled from the S52 Presentation
5870// Library version 3_03 Symbol OWNSHP05
5871static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5872 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5873
5874wxColour ChartCanvas::PredColor() {
5875 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5876 // visibility.
5877 if (SHIP_NORMAL == m_ownship_state)
5878 return GetGlobalColor(_T ( "URED" ));
5879
5880 else if (SHIP_LOWACCURACY == m_ownship_state)
5881 return GetGlobalColor(_T ( "YELO1" ));
5882
5883 return GetGlobalColor(_T ( "NODTA" ));
5884}
5885
5886wxColour ChartCanvas::ShipColor() {
5887 // Establish ship color
5888 // It changes color based on GPS and Chart accuracy/availability
5889
5890 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor(_T ( "GREY1" ));
5891
5892 if (SHIP_LOWACCURACY == m_ownship_state)
5893 return GetGlobalColor(_T ( "YELO1" ));
5894
5895 return GetGlobalColor(_T ( "URED" )); // default is OK
5896}
5897
5898void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5899 wxPoint2DDouble lShipMidPoint) {
5900 dc.SetPen(wxPen(PredColor(), 2));
5901
5902 if (SHIP_NORMAL == m_ownship_state)
5903 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5904 else
5905 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "YELO1" ))));
5906
5907 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5908 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5909
5910 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5911 lShipMidPoint.m_y);
5912 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5913 lShipMidPoint.m_y + 12);
5914}
5915
5916void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5917 wxPoint GPSOffsetPixels,
5918 wxPoint2DDouble lGPSPoint) {
5919 if (m_animationActive) return;
5920 // Develop a uniform length for course predictor line dash length, based on
5921 // physical display size Use this reference length to size all other graphics
5922 // elements
5923 float ref_dim = m_display_size_mm / 24;
5924 ref_dim = wxMin(ref_dim, 12);
5925 ref_dim = wxMax(ref_dim, 6);
5926
5927 wxColour cPred;
5928 cPred.Set(g_cog_predictor_color);
5929 if (cPred == wxNullColour) cPred = PredColor();
5930
5931 // Establish some graphic element line widths dependent on the platform
5932 // display resolution
5933 // double nominal_line_width_pix = wxMax(1.0,
5934 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5935 // not less than 1 pixel
5936 double nominal_line_width_pix = wxMax(
5937 1.0,
5938 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5939
5940 // If the calculated value is greater than the config file spec value, then
5941 // use it.
5942 if (nominal_line_width_pix > g_cog_predictor_width)
5943 g_cog_predictor_width = nominal_line_width_pix;
5944
5945 // Calculate ownship Position Predictor
5946 wxPoint lPredPoint, lHeadPoint;
5947
5948 float pCog = std::isnan(gCog) ? 0 : gCog;
5949 float pSog = std::isnan(gSog) ? 0 : gSog;
5950
5951 double pred_lat, pred_lon;
5952 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5953 &pred_lat, &pred_lon);
5954 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5955
5956 // test to catch the case where COG/HDG line crosses the screen
5957 LLBBox box;
5958
5959 // Should we draw the Head vector?
5960 // Compare the points lHeadPoint and lPredPoint
5961 // If they differ by more than n pixels, and the head vector is valid, then
5962 // render the head vector
5963
5964 float ndelta_pix = 10.;
5965 double hdg_pred_lat, hdg_pred_lon;
5966 bool b_render_hdt = false;
5967 if (!std::isnan(gHdt)) {
5968 // Calculate ownship Heading pointer as a predictor
5969 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5970 &hdg_pred_lon);
5971 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5972 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5973 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5974 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5975 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5976 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5977 }
5978 }
5979
5980 // draw course over ground if they are longer than the ship
5981 wxPoint lShipMidPoint;
5982 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5983 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5984 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5985 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5986
5987 if (lpp >= img_height / 2) {
5988 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5989 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5990 !std::isnan(gSog)) {
5991 // COG Predictor
5992 float dash_length = ref_dim;
5993 wxDash dash_long[2];
5994 dash_long[0] =
5995 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
5996 g_cog_predictor_width); // Long dash , in mm <---------+
5997 dash_long[1] = dash_long[0] / 2.0; // Short gap
5998
5999 // On ultra-hi-res displays, do not allow the dashes to be greater than
6000 // 250, since it is defined as (char)
6001 if (dash_length > 250.) {
6002 dash_long[0] = 250. / g_cog_predictor_width;
6003 dash_long[1] = dash_long[0] / 2;
6004 }
6005
6006 wxPen ppPen2(cPred, g_cog_predictor_width,
6007 (wxPenStyle)g_cog_predictor_style);
6008 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6009 ppPen2.SetDashes(2, dash_long);
6010 dc.SetPen(ppPen2);
6011 dc.StrokeLine(
6012 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6013 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6014
6015 if (g_cog_predictor_width > 1) {
6016 float line_width = g_cog_predictor_width / 3.;
6017
6018 wxDash dash_long3[2];
6019 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6020 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6021
6022 wxPen ppPen3(GetGlobalColor(_T ( "UBLCK" )), wxMax(1, line_width),
6023 (wxPenStyle)g_cog_predictor_style);
6024 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6025 ppPen3.SetDashes(2, dash_long3);
6026 dc.SetPen(ppPen3);
6027 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6028 lGPSPoint.m_y + GPSOffsetPixels.y,
6029 lPredPoint.x + GPSOffsetPixels.x,
6030 lPredPoint.y + GPSOffsetPixels.y);
6031 }
6032
6033 if (g_cog_predictor_endmarker) {
6034 // Prepare COG predictor endpoint icon
6035 double png_pred_icon_scale_factor = .4;
6036 if (g_ShipScaleFactorExp > 1.0)
6037 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6038 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6039
6040 wxPoint icon[4];
6041
6042 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6043 (float)(lPredPoint.x - lShipMidPoint.x));
6044 cog_rad += (float)PI;
6045
6046 for (int i = 0; i < 4; i++) {
6047 int j = i * 2;
6048 double pxa = (double)(s_png_pred_icon[j]);
6049 double pya = (double)(s_png_pred_icon[j + 1]);
6050
6051 pya *= png_pred_icon_scale_factor;
6052 pxa *= png_pred_icon_scale_factor;
6053
6054 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6055 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6056
6057 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6058 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6059 }
6060
6061 // Render COG endpoint icon
6062 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), g_cog_predictor_width / 2,
6063 wxPENSTYLE_SOLID);
6064 dc.SetPen(ppPen1);
6065 dc.SetBrush(wxBrush(cPred));
6066
6067 dc.StrokePolygon(4, icon);
6068 }
6069 }
6070 }
6071
6072 // HDT Predictor
6073 if (b_render_hdt) {
6074 float hdt_dash_length = ref_dim * 0.4;
6075
6076 cPred.Set(g_ownship_HDTpredictor_color);
6077 if (cPred == wxNullColour) cPred = PredColor();
6078 float hdt_width =
6079 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6080 : g_cog_predictor_width * 0.8);
6081 wxDash dash_short[2];
6082 dash_short[0] =
6083 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6084 hdt_width); // Short dash , in mm <---------+
6085 dash_short[1] =
6086 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6087 hdt_width); // Short gap |
6088
6089 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6090 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6091 ppPen2.SetDashes(2, dash_short);
6092
6093 dc.SetPen(ppPen2);
6094 dc.StrokeLine(
6095 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6096 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6097
6098 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6099 dc.SetPen(ppPen1);
6100 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "GREY2" ))));
6101
6102 if (g_ownship_HDTpredictor_endmarker) {
6103 double nominal_circle_size_pixels = wxMax(
6104 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6105
6106 // Scale the circle to ChartScaleFactor, slightly softened....
6107 if (g_ShipScaleFactorExp > 1.0)
6108 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6109
6110 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6111 lHeadPoint.y + GPSOffsetPixels.y,
6112 nominal_circle_size_pixels / 2);
6113 }
6114 }
6115
6116 // Draw radar rings if activated
6117 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6118 double factor = 1.00;
6119 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6120 factor = 1 / 1.852;
6121 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6122 if (std::isnan(gSog))
6123 factor = 0.0;
6124 else
6125 factor = gSog / 60;
6126 }
6127 factor *= g_fNavAidRadarRingsStep;
6128
6129 double tlat, tlon;
6130 wxPoint r;
6131 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6132 GetCanvasPointPix(tlat, tlon, &r);
6133
6134 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6135 pow((double)(lGPSPoint.m_y - r.y), 2));
6136 int pix_radius = (int)lpp;
6137
6138 extern wxColor GetDimColor(wxColor c);
6139 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6140
6141 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6142
6143 dc.SetPen(ppPen1);
6144 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6145
6146 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6147 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6148 }
6149}
6150
6151void ChartCanvas::ComputeShipScaleFactor(
6152 float icon_hdt, int ownShipWidth, int ownShipLength,
6153 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6154 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6155 float screenResolution = m_pix_per_mm;
6156
6157 // Calculate the true ship length in exact pixels
6158 double ship_bow_lat, ship_bow_lon;
6159 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6160 &ship_bow_lat, &ship_bow_lon);
6161 wxPoint lShipBowPoint;
6162 wxPoint2DDouble b_point =
6163 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6164 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6165
6166 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6167 powf((float)(b_point.m_y - a_point.m_y), 2));
6168
6169 // And in mm
6170 float shipLength_mm = shipLength_px / screenResolution;
6171
6172 // Set minimum ownship drawing size
6173 float ownship_min_mm = g_n_ownship_min_mm;
6174 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6175
6176 // Calculate Nautical Miles distance from midships to gps antenna
6177 float hdt_ant = icon_hdt + 180.;
6178 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6179 float dx = g_n_gps_antenna_offset_x / 1852.;
6180 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6181 {
6182 hdt_ant = icon_hdt;
6183 dy = -dy;
6184 }
6185
6186 // If the drawn ship size is going to be clamped, adjust the gps antenna
6187 // offsets
6188 if (shipLength_mm < ownship_min_mm) {
6189 dy /= shipLength_mm / ownship_min_mm;
6190 dx /= shipLength_mm / ownship_min_mm;
6191 }
6192
6193 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6194
6195 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6196 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6197 &ship_mid_lon1);
6198
6199 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6200 &lShipMidPoint);
6201
6202 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6203 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6204
6205 float scale_factor = shipLength_px / ownShipLength;
6206
6207 // Calculate a scale factor that would produce a reasonably sized icon
6208 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6209
6210 // And choose the correct one
6211 scale_factor = wxMax(scale_factor, scale_factor_min);
6212
6213 scale_factor_y = scale_factor;
6214 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6215 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6216}
6217
6218void ChartCanvas::ShipDraw(ocpnDC &dc) {
6219 if (!GetVP().IsValid()) return;
6220
6221 wxPoint GPSOffsetPixels(0, 0);
6222 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6223
6224 // COG/SOG may be undefined in NMEA data stream
6225 float pCog = std::isnan(gCog) ? 0 : gCog;
6226 float pSog = std::isnan(gSog) ? 0 : gSog;
6227
6228 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6229
6230 lShipMidPoint = lGPSPoint;
6231
6232 // Draw the icon rotated to the COG
6233 // or to the Hdt if available
6234 float icon_hdt = pCog;
6235 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6236
6237 // COG may be undefined in NMEA data stream
6238 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6239
6240 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6241 // predictor
6242 double osd_head_lat, osd_head_lon;
6243 wxPoint osd_head_point;
6244
6245 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6246 &osd_head_lon);
6247
6248 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6249
6250 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6251 (float)(osd_head_point.x - lShipMidPoint.m_x));
6252 icon_rad += (float)PI;
6253
6254 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6255
6256 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6257 // nominal size and is just barely outside the viewport ....
6258 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6259
6260 // TODO: fix to include actual size of boat that will be rendered
6261 int img_height = 0;
6262 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6263 if (GetVP().chart_scale >
6264 300000) // According to S52, this should be 50,000
6265 {
6266 ShipDrawLargeScale(dc, lShipMidPoint);
6267 img_height = 20;
6268 } else {
6269 wxImage pos_image;
6270
6271 // Substitute user ownship image if found
6272 if (m_pos_image_user)
6273 pos_image = m_pos_image_user->Copy();
6274 else if (SHIP_NORMAL == m_ownship_state)
6275 pos_image = m_pos_image_red->Copy();
6276 if (SHIP_LOWACCURACY == m_ownship_state)
6277 pos_image = m_pos_image_yellow->Copy();
6278 else if (SHIP_NORMAL != m_ownship_state)
6279 pos_image = m_pos_image_grey->Copy();
6280
6281 // Substitute user ownship image if found
6282 if (m_pos_image_user) {
6283 pos_image = m_pos_image_user->Copy();
6284
6285 if (SHIP_LOWACCURACY == m_ownship_state)
6286 pos_image = m_pos_image_user_yellow->Copy();
6287 else if (SHIP_NORMAL != m_ownship_state)
6288 pos_image = m_pos_image_user_grey->Copy();
6289 }
6290
6291 img_height = pos_image.GetHeight();
6292
6293 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6294 g_OwnShipIconType > 0) // use large ship
6295 {
6296 int ownShipWidth = 22; // Default values from s_ownship_icon
6297 int ownShipLength = 84;
6298 if (g_OwnShipIconType == 1) {
6299 ownShipWidth = pos_image.GetWidth();
6300 ownShipLength = pos_image.GetHeight();
6301 }
6302
6303 float scale_factor_x, scale_factor_y;
6304 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6305 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6306 scale_factor_x, scale_factor_y);
6307
6308 if (g_OwnShipIconType == 1) { // Scaled bitmap
6309 pos_image.Rescale(ownShipWidth * scale_factor_x,
6310 ownShipLength * scale_factor_y,
6311 wxIMAGE_QUALITY_HIGH);
6312 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6313 wxImage rot_image =
6314 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6315
6316 // Simple sharpening algorithm.....
6317 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6318 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6319 if (rot_image.GetAlpha(ip, jp) > 64)
6320 rot_image.SetAlpha(ip, jp, 255);
6321
6322 wxBitmap os_bm(rot_image);
6323
6324 int w = os_bm.GetWidth();
6325 int h = os_bm.GetHeight();
6326 img_height = h;
6327
6328 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6329 lShipMidPoint.m_y - h / 2, true);
6330
6331 // Maintain dirty box,, missing in __WXMSW__ library
6332 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6333 lShipMidPoint.m_y - h / 2);
6334 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6335 lShipMidPoint.m_y - h / 2 + h);
6336 }
6337
6338 else if (g_OwnShipIconType == 2) { // Scaled Vector
6339 wxPoint ownship_icon[10];
6340
6341 for (int i = 0; i < 10; i++) {
6342 int j = i * 2;
6343 float pxa = (float)(s_ownship_icon[j]);
6344 float pya = (float)(s_ownship_icon[j + 1]);
6345 pya *= scale_factor_y;
6346 pxa *= scale_factor_x;
6347
6348 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6349 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6350
6351 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6352 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6353 }
6354
6355 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), 1, wxPENSTYLE_SOLID);
6356 dc.SetPen(ppPen1);
6357 dc.SetBrush(wxBrush(ShipColor()));
6358
6359 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6360
6361 // draw reference point (midships) cross
6362 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6363 ownship_icon[7].y);
6364 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6365 ownship_icon[9].y);
6366 }
6367
6368 img_height = ownShipLength * scale_factor_y;
6369
6370 // Reference point, where the GPS antenna is
6371 int circle_rad = 3;
6372 if (m_pos_image_user) circle_rad = 1;
6373
6374 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6375 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6376 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6377 } else { // Fixed bitmap icon.
6378 /* non opengl, or suboptimal opengl via ocpndc: */
6379 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6380 wxImage rot_image =
6381 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6382
6383 // Simple sharpening algorithm.....
6384 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6385 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6386 if (rot_image.GetAlpha(ip, jp) > 64)
6387 rot_image.SetAlpha(ip, jp, 255);
6388
6389 wxBitmap os_bm(rot_image);
6390
6391 if (g_ShipScaleFactorExp > 1) {
6392 wxImage scaled_image = os_bm.ConvertToImage();
6393 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6394 1.0; // soften the scale factor a bit
6395 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6396 scaled_image.GetHeight() * factor,
6397 wxIMAGE_QUALITY_HIGH));
6398 }
6399 int w = os_bm.GetWidth();
6400 int h = os_bm.GetHeight();
6401 img_height = h;
6402
6403 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6404 lShipMidPoint.m_y - h / 2, true);
6405
6406 // Reference point, where the GPS antenna is
6407 int circle_rad = 3;
6408 if (m_pos_image_user) circle_rad = 1;
6409
6410 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6411 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6412 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6413
6414 // Maintain dirty box,, missing in __WXMSW__ library
6415 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6416 lShipMidPoint.m_y - h / 2);
6417 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6418 lShipMidPoint.m_y - h / 2 + h);
6419 }
6420 } // ownship draw
6421 }
6422
6423 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6424}
6425
6426/* @ChartCanvas::CalcGridSpacing
6427 **
6428 ** Calculate the major and minor spacing between the lat/lon grid
6429 **
6430 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6431 *window
6432 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6433 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6434 ** @return [void]
6435 */
6436void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6437 float &MinorSpacing) {
6438 // table for calculating the distance between the grids
6439 // [0] view_scale ppm
6440 // [1] spacing between major grid lines in degrees
6441 // [2] spacing between minor grid lines in degrees
6442 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6443 {.000001f, 45.0f, 15.0f},
6444 {.0002f, 30.0f, 10.0f},
6445 {.0003f, 10.0f, 2.0f},
6446 {.0008f, 5.0f, 1.0f},
6447 {.001f, 2.0f, 30.0f / 60.0f},
6448 {.003f, 1.0f, 20.0f / 60.0f},
6449 {.006f, 0.5f, 10.0f / 60.0f},
6450 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6451 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6452 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6453 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6454 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6455 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6456 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6457 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6458
6459 unsigned int tabi;
6460 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6461 if (view_scale_ppm < lltab[tabi][0]) break;
6462 MajorSpacing = lltab[tabi][1]; // major latitude distance
6463 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6464 return;
6465}
6466/* @ChartCanvas::CalcGridText *************************************
6467 **
6468 ** Calculates text to display at the major grid lines
6469 **
6470 ** @param [r] latlon [float] latitude or longitude of grid line
6471 ** @param [r] spacing [float] distance between two major grid lines
6472 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6473 **
6474 ** @return
6475 */
6476
6477wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6478 int deg = (int)fabs(latlon); // degrees
6479 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6480 char postfix;
6481
6482 // calculate postfix letter (NSEW)
6483 if (latlon > 0.0) {
6484 if (bPostfix) {
6485 postfix = 'N';
6486 } else {
6487 postfix = 'E';
6488 }
6489 } else if (latlon < 0.0) {
6490 if (bPostfix) {
6491 postfix = 'S';
6492 } else {
6493 postfix = 'W';
6494 }
6495 } else {
6496 postfix = ' '; // no postfix for equator and greenwich
6497 }
6498 // calculate text, display minutes only if spacing is smaller than one degree
6499
6500 wxString ret;
6501 if (spacing >= 1.0) {
6502 ret.Printf(_T("%3d%c %c"), deg, 0x00b0, postfix);
6503 } else if (spacing >= (1.0 / 60.0)) {
6504 ret.Printf(_T("%3d%c%02.0f %c"), deg, 0x00b0, min, postfix);
6505 } else {
6506 ret.Printf(_T("%3d%c%02.2f %c"), deg, 0x00b0, min, postfix);
6507 }
6508
6509 return ret;
6510}
6511
6512/* @ChartCanvas::GridDraw *****************************************
6513 **
6514 ** Draws major and minor Lat/Lon Grid on the chart
6515 ** - distance between Grid-lm ines are calculated automatic
6516 ** - major grid lines will be across the whole chart window
6517 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6518 **
6519 ** @param [w] dc [wxDC&] the wx drawing context
6520 **
6521 ** @return [void]
6522 ************************************************************************/
6523void ChartCanvas::GridDraw(ocpnDC &dc) {
6524 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6525
6526 double nlat, elon, slat, wlon;
6527 float lat, lon;
6528 float dlon;
6529 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6530 wxCoord w, h;
6531 wxPen GridPen(GetGlobalColor(_T ( "SNDG1" )), 1, wxPENSTYLE_SOLID);
6532 dc.SetPen(GridPen);
6533 dc.SetFont(*m_pgridFont);
6534 dc.SetTextForeground(GetGlobalColor(_T ( "SNDG1" )));
6535
6536 w = m_canvas_width;
6537 h = m_canvas_height;
6538
6539 GetCanvasPixPoint(0, 0, nlat,
6540 wlon); // get lat/lon of upper left point of the window
6541 GetCanvasPixPoint(w, h, slat,
6542 elon); // get lat/lon of lower right point of the window
6543 dlon =
6544 elon -
6545 wlon; // calculate how many degrees of longitude are shown in the window
6546 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6547 {
6548 dlon = dlon + 360.0;
6549 }
6550 // calculate distance between latitude grid lines
6551 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6552
6553 // calculate position of first major latitude grid line
6554 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6555
6556 // Draw Major latitude grid lines and text
6557 while (lat < nlat) {
6558 wxPoint r;
6559 wxString st =
6560 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6561 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6562 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6563 dc.DrawText(st, 0, r.y); // draw text
6564 lat = lat + gridlatMajor;
6565
6566 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6567 }
6568
6569 // calculate position of first minor latitude grid line
6570 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6571
6572 // Draw minor latitude grid lines
6573 while (lat < nlat) {
6574 wxPoint r;
6575 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6576 dc.DrawLine(0, r.y, 10, r.y, false);
6577 dc.DrawLine(w - 10, r.y, w, r.y, false);
6578 lat = lat + gridlatMinor;
6579 }
6580
6581 // calculate distance between grid lines
6582 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6583
6584 // calculate position of first major latitude grid line
6585 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6586
6587 // draw major longitude grid lines
6588 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6589 wxPoint r;
6590 wxString st = CalcGridText(lon, gridlonMajor, false);
6591 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6592 dc.DrawLine(r.x, 0, r.x, h, false);
6593 dc.DrawText(st, r.x, 0);
6594 lon = lon + gridlonMajor;
6595 if (lon > 180.0) {
6596 lon = lon - 360.0;
6597 }
6598
6599 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6600 }
6601
6602 // calculate position of first minor longitude grid line
6603 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6604 // draw minor longitude grid lines
6605 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6606 wxPoint r;
6607 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6608 dc.DrawLine(r.x, 0, r.x, 10, false);
6609 dc.DrawLine(r.x, h - 10, r.x, h, false);
6610 lon = lon + gridlonMinor;
6611 if (lon > 180.0) {
6612 lon = lon - 360.0;
6613 }
6614 }
6615}
6616
6617void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6618 if (0 ) {
6619 double blat, blon, tlat, tlon;
6620 wxPoint r;
6621
6622 int x_origin = m_bDisplayGrid ? 60 : 20;
6623 int y_origin = m_canvas_height - 50;
6624
6625 float dist;
6626 int count;
6627 wxPen pen1, pen2;
6628
6629 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6630 {
6631 dist = 10.0;
6632 count = 5;
6633 pen1 = wxPen(GetGlobalColor(_T ( "SNDG2" )), 3, wxPENSTYLE_SOLID);
6634 pen2 = wxPen(GetGlobalColor(_T ( "SNDG1" )), 3, wxPENSTYLE_SOLID);
6635 } else // Draw 1 mile scale as SCALEB10
6636 {
6637 dist = 1.0;
6638 count = 10;
6639 pen1 = wxPen(GetGlobalColor(_T ( "SCLBR" )), 3, wxPENSTYLE_SOLID);
6640 pen2 = wxPen(GetGlobalColor(_T ( "CHGRD" )), 3, wxPENSTYLE_SOLID);
6641 }
6642
6643 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6644 double rotation = -VPoint.rotation;
6645
6646 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6647 GetCanvasPointPix(tlat, tlon, &r);
6648 int l1 = (y_origin - r.y) / count;
6649
6650 for (int i = 0; i < count; i++) {
6651 int y = l1 * i;
6652 if (i & 1)
6653 dc.SetPen(pen1);
6654 else
6655 dc.SetPen(pen2);
6656
6657 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6658 }
6659 } else {
6660 double blat, blon, tlat, tlon;
6661
6662 int x_origin = 5.0 * GetPixPerMM();
6663 int chartbar_height = GetChartbarHeight();
6664 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6665 // if (style->chartStatusWindowTransparent)
6666 // chartbar_height = 0;
6667 int y_origin = m_canvas_height - chartbar_height - 5;
6668#ifdef __WXOSX__
6669 if (!g_bopengl)
6670 y_origin =
6671 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6672#endif
6673
6674 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6675 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6676
6677 double d;
6678 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6679 d /= 2;
6680
6681 int unit = g_iDistanceFormat;
6682 if (d < .5 &&
6683 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6684 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6685
6686 // nice number
6687 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6688 float places = floor(logdist), rem = logdist - places;
6689 dist = pow(10, places);
6690
6691 if (rem < .2)
6692 dist /= 5;
6693 else if (rem < .5)
6694 dist /= 2;
6695
6696 wxString s = wxString::Format(_T("%g "), dist) + getUsrDistanceUnit(unit);
6697 wxPen pen1 = wxPen(GetGlobalColor(_T ( "UBLCK" )), 3, wxPENSTYLE_SOLID);
6698 double rotation = -VPoint.rotation;
6699
6700 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6701 &tlat, &tlon);
6702 wxPoint r;
6703 GetCanvasPointPix(tlat, tlon, &r);
6704 int l1 = r.x - x_origin;
6705
6706 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6707 12); // Store this for later reference
6708
6709 dc.SetPen(pen1);
6710
6711 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6712 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6713 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6714
6715 dc.SetFont(*m_pgridFont);
6716 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
6717 int w, h;
6718 dc.GetTextExtent(s, &w, &h);
6719 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6720 }
6721}
6722
6723void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6724 // Constants?
6725 double da_min = 2.;
6726 double da_max = 6.;
6727 double ra_min = 0.;
6728 double ra_max = 40.;
6729
6730 wxPen pen_save = dc.GetPen();
6731
6732 wxDateTime now = wxDateTime::Now();
6733
6734 dc.SetPen(pen);
6735
6736 int x0, y0, x1, y1;
6737
6738 x0 = x1 = x + radius; // Start point
6739 y0 = y1 = y;
6740 double angle = 0.;
6741 int i = 0;
6742
6743 while (angle < 360.) {
6744 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6745 angle += da;
6746
6747 if (angle > 360.) angle = 360.;
6748
6749 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6750
6751 double r;
6752 if (i & 1)
6753 r = radius + ra;
6754 else
6755 r = radius - ra;
6756
6757 x1 = (int)(x + cos(angle * PI / 180.) * r);
6758 y1 = (int)(y + sin(angle * PI / 180.) * r);
6759
6760 dc.DrawLine(x0, y0, x1, y1);
6761
6762 x0 = x1;
6763 y0 = y1;
6764
6765 i++;
6766 }
6767
6768 dc.DrawLine(x + radius, y, x1, y1); // closure
6769
6770 dc.SetPen(pen_save);
6771}
6772
6773static bool bAnchorSoundPlaying = false;
6774
6775static void onAnchorSoundFinished(void *ptr) {
6776 g_anchorwatch_sound->UnLoad();
6777 bAnchorSoundPlaying = false;
6778}
6779
6780void ChartCanvas::AlertDraw(ocpnDC &dc) {
6781 // Visual and audio alert for anchorwatch goes here
6782 bool play_sound = false;
6783 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6784 if (AnchorAlertOn1) {
6785 wxPoint TargetPoint;
6786 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
6787 &TargetPoint);
6788 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6789 TargetPoint.y, 100);
6790 play_sound = true;
6791 }
6792 } else
6793 AnchorAlertOn1 = false;
6794
6795 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6796 if (AnchorAlertOn2) {
6797 wxPoint TargetPoint;
6798 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
6799 &TargetPoint);
6800 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6801 TargetPoint.y, 100);
6802 play_sound = true;
6803 }
6804 } else
6805 AnchorAlertOn2 = false;
6806
6807 if (play_sound) {
6808 if (!bAnchorSoundPlaying) {
6809 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6810 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6811 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6812 if (g_anchorwatch_sound->IsOk()) {
6813 bAnchorSoundPlaying = true;
6814 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6815 g_anchorwatch_sound->Play();
6816 }
6817 }
6818 }
6819}
6820
6821void ChartCanvas::UpdateShips() {
6822 // Get the rectangle in the current dc which bounds the "ownship" symbol
6823
6824 wxClientDC dc(this);
6825 if (!dc.IsOk()) return;
6826
6827 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6828 if (!test_bitmap.IsOk()) return;
6829
6830 wxMemoryDC temp_dc(test_bitmap);
6831
6832 temp_dc.ResetBoundingBox();
6833 temp_dc.DestroyClippingRegion();
6834 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6835
6836 // Draw the ownship on the temp_dc
6837 ocpnDC ocpndc = ocpnDC(temp_dc);
6838 ShipDraw(ocpndc);
6839
6840 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6841 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6842 if (p) {
6843 wxPoint px;
6844 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6845 ocpndc.CalcBoundingBox(px.x, px.y);
6846 }
6847 }
6848
6849 ship_draw_rect =
6850 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6851 temp_dc.MaxY() - temp_dc.MinY());
6852
6853 wxRect own_ship_update_rect = ship_draw_rect;
6854
6855 if (!own_ship_update_rect.IsEmpty()) {
6856 // The required invalidate rectangle is the union of the last drawn
6857 // rectangle and this drawn rectangle
6858 own_ship_update_rect.Union(ship_draw_last_rect);
6859 own_ship_update_rect.Inflate(2);
6860 }
6861
6862 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6863
6864 ship_draw_last_rect = ship_draw_rect;
6865
6866 temp_dc.SelectObject(wxNullBitmap);
6867}
6868
6869void ChartCanvas::UpdateAlerts() {
6870 // Get the rectangle in the current dc which bounds the detected Alert
6871 // targets
6872
6873 // Use this dc
6874 wxClientDC dc(this);
6875
6876 // Get dc boundary
6877 int sx, sy;
6878 dc.GetSize(&sx, &sy);
6879
6880 // Need a bitmap
6881 wxBitmap test_bitmap(sx, sy, -1);
6882
6883 // Create a memory DC
6884 wxMemoryDC temp_dc;
6885 temp_dc.SelectObject(test_bitmap);
6886
6887 temp_dc.ResetBoundingBox();
6888 temp_dc.DestroyClippingRegion();
6889 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6890
6891 // Draw the Alert Targets on the temp_dc
6892 ocpnDC ocpndc = ocpnDC(temp_dc);
6893 AlertDraw(ocpndc);
6894
6895 // Retrieve the drawing extents
6896 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6897 temp_dc.MaxX() - temp_dc.MinX(),
6898 temp_dc.MaxY() - temp_dc.MinY());
6899
6900 if (!alert_rect.IsEmpty())
6901 alert_rect.Inflate(2); // clear all drawing artifacts
6902
6903 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6904 // The required invalidate rectangle is the union of the last drawn
6905 // rectangle and this drawn rectangle
6906 wxRect alert_update_rect = alert_draw_rect;
6907 alert_update_rect.Union(alert_rect);
6908
6909 // Invalidate the rectangular region
6910 RefreshRect(alert_update_rect, false);
6911 }
6912
6913 // Save this rectangle for next time
6914 alert_draw_rect = alert_rect;
6915
6916 temp_dc.SelectObject(wxNullBitmap); // clean up
6917}
6918
6919void ChartCanvas::UpdateAIS() {
6920 if (!g_pAIS) return;
6921
6922 // Get the rectangle in the current dc which bounds the detected AIS targets
6923
6924 // Use this dc
6925 wxClientDC dc(this);
6926
6927 // Get dc boundary
6928 int sx, sy;
6929 dc.GetSize(&sx, &sy);
6930
6931 wxRect ais_rect;
6932
6933 // How many targets are there?
6934
6935 // If more than "some number", it will be cheaper to refresh the entire
6936 // screen than to build update rectangles for each target.
6937 if (g_pAIS->GetTargetList().size() > 10) {
6938 ais_rect = wxRect(0, 0, sx, sy); // full screen
6939 } else {
6940 // Need a bitmap
6941 wxBitmap test_bitmap(sx, sy, -1);
6942
6943 // Create a memory DC
6944 wxMemoryDC temp_dc;
6945 temp_dc.SelectObject(test_bitmap);
6946
6947 temp_dc.ResetBoundingBox();
6948 temp_dc.DestroyClippingRegion();
6949 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6950
6951 // Draw the AIS Targets on the temp_dc
6952 ocpnDC ocpndc = ocpnDC(temp_dc);
6953 AISDraw(ocpndc, GetVP(), this);
6954 AISDrawAreaNotices(ocpndc, GetVP(), this);
6955
6956 // Retrieve the drawing extents
6957 ais_rect =
6958 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6959 temp_dc.MaxY() - temp_dc.MinY());
6960
6961 if (!ais_rect.IsEmpty())
6962 ais_rect.Inflate(2); // clear all drawing artifacts
6963
6964 temp_dc.SelectObject(wxNullBitmap); // clean up
6965 }
6966
6967 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6968 // The required invalidate rectangle is the union of the last drawn
6969 // rectangle and this drawn rectangle
6970 wxRect ais_update_rect = ais_draw_rect;
6971 ais_update_rect.Union(ais_rect);
6972
6973 // Invalidate the rectangular region
6974 RefreshRect(ais_update_rect, false);
6975 }
6976
6977 // Save this rectangle for next time
6978 ais_draw_rect = ais_rect;
6979}
6980
6981void ChartCanvas::ToggleCPAWarn() {
6982 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6983 wxString mess;
6984 if (g_bCPAWarn) {
6985 g_bTCPA_Max = true;
6986 mess = _("ON");
6987 } else {
6988 g_bTCPA_Max = false;
6989 mess = _("OFF");
6990 }
6991 // Print to status bar if available.
6992 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
6993 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
6994 } else {
6995 if (!g_AisFirstTimeUse) {
6996 OCPNMessageBox(this,
6997 _("CPA Alarm is switched") + _T(" ") + mess.MakeLower(),
6998 _("CPA") + _T(" ") + mess, 4, 4);
6999 }
7000 }
7001}
7002
7003void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7004
7005void ChartCanvas::OnSize(wxSizeEvent &event) {
7006 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7007 // GetClientSize returns the size of the canvas area in logical pixels.
7008 GetClientSize(&m_canvas_width, &m_canvas_height);
7009
7010#ifdef __WXOSX__
7011 // Support scaled HDPI displays.
7012 m_displayScale = GetContentScaleFactor();
7013#endif
7014
7015 // Convert to physical pixels.
7016 m_canvas_width *= m_displayScale;
7017 m_canvas_height *= m_displayScale;
7018
7019 // Resize the current viewport
7020 VPoint.pix_width = m_canvas_width;
7021 VPoint.pix_height = m_canvas_height;
7022 VPoint.SetPixelScale(m_displayScale);
7023
7024 // Get some canvas metrics
7025
7026 // Rescale to current value, in order to rebuild VPoint data
7027 // structures for new canvas size
7029
7030 m_absolute_min_scale_ppm =
7031 m_canvas_width /
7032 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7033
7034 // Inform the parent Frame that I am being resized...
7035 gFrame->ProcessCanvasResize();
7036
7037 // if MUIBar is active, size the bar
7038 // if(g_useMUI && !m_muiBar){ // rebuild if
7039 // necessary
7040 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7041 // m_muiBarHOSize = m_muiBar->GetSize();
7042 // }
7043
7044 if (m_muiBar) {
7045 SetMUIBarPosition();
7046 UpdateFollowButtonState();
7047 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7048 }
7049
7050 // Set up the scroll margins
7051 xr_margin = m_canvas_width * 95 / 100;
7052 xl_margin = m_canvas_width * 5 / 100;
7053 yt_margin = m_canvas_height * 5 / 100;
7054 yb_margin = m_canvas_height * 95 / 100;
7055
7056 if (m_pQuilt)
7057 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7058
7059 // Resize the scratch BM
7060 delete pscratch_bm;
7061 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7062 m_brepaint_piano = true;
7063
7064 // Resize the Route Calculation BM
7065 m_dc_route.SelectObject(wxNullBitmap);
7066 delete proute_bm;
7067 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7068 m_dc_route.SelectObject(*proute_bm);
7069
7070 // Resize the saved Bitmap
7071 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7072
7073 // Resize the working Bitmap
7074 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7075
7076 // Rescale again, to capture all the changes for new canvas size
7078
7079#ifdef ocpnUSE_GL
7080 if (/*g_bopengl &&*/ m_glcc) {
7081 // FIXME (dave) This can go away?
7082 m_glcc->OnSize(event);
7083 }
7084#endif
7085
7086 FormatPianoKeys();
7087 // Invalidate the whole window
7088 ReloadVP();
7089}
7090
7091void ChartCanvas::ProcessNewGUIScale() {
7092 // m_muiBar->Hide();
7093 delete m_muiBar;
7094 m_muiBar = 0;
7095
7096 CreateMUIBar();
7097}
7098
7099void ChartCanvas::CreateMUIBar() {
7100 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7101
7102 // We need to update the m_bENCGroup flag, at least for the initial creation
7103 // of a MUIBar
7104 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7105
7106 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7107 m_muiBar->SetColorScheme(m_cs);
7108 m_muiBarHOSize = m_muiBar->m_size;
7109 }
7110
7111 if (m_muiBar) {
7112 SetMUIBarPosition();
7113 UpdateFollowButtonState();
7114 m_muiBar->UpdateDynamicValues();
7115 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7116 }
7117}
7118
7119void ChartCanvas::SetMUIBarPosition() {
7120 // if MUIBar is active, size the bar
7121 if (m_muiBar) {
7122 // We estimate the piano width based on the canvas width
7123 int pianoWidth = GetClientSize().x * 0.6f;
7124 // If the piano already exists, we can use its exact width
7125 // if(m_Piano)
7126 // pianoWidth = m_Piano->GetWidth();
7127
7128 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7129 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7130 delete m_muiBar;
7131 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7132 m_muiBar->SetColorScheme(m_cs);
7133 }
7134 }
7135
7136 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7137 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7138 delete m_muiBar;
7139 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7140 m_muiBar->SetColorScheme(m_cs);
7141 }
7142 }
7143
7144 m_muiBar->SetBestPosition();
7145 }
7146}
7147
7148void ChartCanvas::DestroyMuiBar() {
7149 if (m_muiBar) {
7150 delete m_muiBar;
7151 m_muiBar = NULL;
7152 }
7153}
7154
7155void ChartCanvas::ShowCompositeInfoWindow(
7156 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7157 if (n_charts > 0) {
7158 if (NULL == m_pCIWin) {
7159 m_pCIWin = new ChInfoWin(this);
7160 m_pCIWin->Hide();
7161 }
7162
7163 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7164 wxString s;
7165
7166 s = _("Composite of ");
7167
7168 wxString s1;
7169 s1.Printf("%d ", n_charts);
7170 if (n_charts > 1)
7171 s1 += _("charts");
7172 else
7173 s1 += _("chart");
7174 s += s1;
7175 s += '\n';
7176
7177 s1.Printf(_("Chart scale"));
7178 s1 += ": ";
7179 wxString s2;
7180 s2.Printf("1:%d\n", scale);
7181 s += s1;
7182 s += s2;
7183
7184 s1 = _("Zoom in for more information");
7185 s += s1;
7186 s += '\n';
7187
7188 int char_width = s1.Length();
7189 int char_height = 3;
7190
7191 if (g_bChartBarEx) {
7192 s += '\n';
7193 int j = 0;
7194 for (int i : index_vector) {
7195 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7196 wxString path = cte.GetFullSystemPath();
7197 s += path;
7198 s += '\n';
7199 char_height++;
7200 char_width = wxMax(char_width, path.Length());
7201 if (j++ >= 9) break;
7202 }
7203 if (j >= 9) {
7204 s += " .\n .\n .\n";
7205 char_height += 3;
7206 }
7207 s += '\n';
7208 char_height += 1;
7209
7210 char_width += 4; // Fluff
7211 }
7212
7213 m_pCIWin->SetString(s);
7214
7215 m_pCIWin->FitToChars(char_width, char_height);
7216
7217 wxPoint p;
7218 p.x = x / GetContentScaleFactor();
7219 if ((p.x + m_pCIWin->GetWinSize().x) >
7220 (m_canvas_width / GetContentScaleFactor()))
7221 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7222 m_pCIWin->GetWinSize().x) /
7223 2; // centered
7224
7225 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7226 4 - m_pCIWin->GetWinSize().y;
7227
7228 m_pCIWin->dbIndex = 0;
7229 m_pCIWin->chart_scale = 0;
7230 m_pCIWin->SetPosition(p);
7231 m_pCIWin->SetBitmap();
7232 m_pCIWin->Refresh();
7233 m_pCIWin->Show();
7234 }
7235 } else {
7236 HideChartInfoWindow();
7237 }
7238}
7239
7240void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7241 if (dbIndex >= 0) {
7242 if (NULL == m_pCIWin) {
7243 m_pCIWin = new ChInfoWin(this);
7244 m_pCIWin->Hide();
7245 }
7246
7247 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7248 wxString s;
7249 ChartBase *pc = NULL;
7250
7251 // TOCTOU race but worst case will reload chart.
7252 // need to lock it or the background spooler may evict charts in
7253 // OpenChartFromDBAndLock
7254 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7255 pc = ChartData->OpenChartFromDBAndLock(
7256 dbIndex, FULL_INIT); // this must come from cache
7257
7258 int char_width, char_height;
7259 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7260 if (pc) ChartData->UnLockCacheChart(dbIndex);
7261
7262 m_pCIWin->SetString(s);
7263 m_pCIWin->FitToChars(char_width, char_height);
7264
7265 wxPoint p;
7266 p.x = x / GetContentScaleFactor();
7267 if ((p.x + m_pCIWin->GetWinSize().x) >
7268 (m_canvas_width / GetContentScaleFactor()))
7269 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7270 m_pCIWin->GetWinSize().x) /
7271 2; // centered
7272
7273 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7274 4 - m_pCIWin->GetWinSize().y;
7275
7276 m_pCIWin->dbIndex = dbIndex;
7277 m_pCIWin->SetPosition(p);
7278 m_pCIWin->SetBitmap();
7279 m_pCIWin->Refresh();
7280 m_pCIWin->Show();
7281 }
7282 } else {
7283 HideChartInfoWindow();
7284 }
7285}
7286
7287void ChartCanvas::HideChartInfoWindow(void) {
7288 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7289 m_pCIWin->Hide();
7290 m_pCIWin->Destroy();
7291 m_pCIWin = NULL;
7292
7293#ifdef __ANDROID__
7294 androidForceFullRepaint();
7295#endif
7296 }
7297}
7298
7299void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7300 wxMouseEvent ev(wxEVT_MOTION);
7301 ev.m_x = mouse_x;
7302 ev.m_y = mouse_y;
7303 ev.m_leftDown = mouse_leftisdown;
7304
7305 wxEvtHandler *evthp = GetEventHandler();
7306
7307 ::wxPostEvent(evthp, ev);
7308}
7309
7310void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7311 if ((m_panx_target_final - m_panx_target_now) ||
7312 (m_pany_target_final - m_pany_target_now)) {
7313 DoTimedMovementTarget();
7314 } else
7315 DoTimedMovement();
7316}
7317
7318void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7319
7320bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7321 int delta) {
7322 if (m_disable_edge_pan) return false;
7323
7324 bool bft = false;
7325 int pan_margin = m_canvas_width * margin / 100;
7326 int pan_timer_set = 200;
7327 double pan_delta = GetVP().pix_width * delta / 100;
7328 int pan_x = 0;
7329 int pan_y = 0;
7330
7331 if (x > m_canvas_width - pan_margin) {
7332 bft = true;
7333 pan_x = pan_delta;
7334 }
7335
7336 else if (x < pan_margin) {
7337 bft = true;
7338 pan_x = -pan_delta;
7339 }
7340
7341 if (y < pan_margin) {
7342 bft = true;
7343 pan_y = -pan_delta;
7344 }
7345
7346 else if (y > m_canvas_height - pan_margin) {
7347 bft = true;
7348 pan_y = pan_delta;
7349 }
7350
7351 // Of course, if dragging, and the mouse left button is not down, we must
7352 // stop the event injection
7353 if (bdragging) {
7354 if (!g_btouch) {
7355 wxMouseState state = ::wxGetMouseState();
7356#if wxCHECK_VERSION(3, 0, 0)
7357 if (!state.LeftIsDown())
7358#else
7359 if (!state.LeftDown())
7360#endif
7361 bft = false;
7362 }
7363 }
7364 if ((bft) && !pPanTimer->IsRunning()) {
7365 PanCanvas(pan_x, pan_y);
7366 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7367 return true;
7368 }
7369
7370 // This mouse event must not be due to pan timer event injector
7371 // Mouse is out of the pan zone, so prevent any orphan event injection
7372 if ((!bft) && pPanTimer->IsRunning()) {
7373 pPanTimer->Stop();
7374 }
7375
7376 return (false);
7377}
7378
7379// Look for waypoints at the current position.
7380// Used to determine what a mouse event should act on.
7381
7382void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7383 bool setBeingEdited) {
7384 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7385 m_pRoutePointEditTarget = NULL;
7386 m_pFoundPoint = NULL;
7387
7388 SelectItem *pFind = NULL;
7389 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7390 SelectableItemList SelList = pSelect->FindSelectionList(
7391 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7392 wxSelectableItemListNode *node = SelList.GetFirst();
7393 while (node) {
7394 pFind = node->GetData();
7395
7396 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7397
7398 // Get an array of all routes using this point
7399 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7400 // TODO: delete m_pEditRouteArray after use?
7401
7402 // Use route array to determine actual visibility for the point
7403 bool brp_viz = false;
7404 if (m_pEditRouteArray) {
7405 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7406 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7407 if (pr->IsVisible()) {
7408 brp_viz = true;
7409 break;
7410 }
7411 }
7412 } else
7413 brp_viz = frp->IsVisible(); // isolated point
7414
7415 if (brp_viz) {
7416 // Use route array to rubberband all affected routes
7417 if (m_pEditRouteArray) // Editing Waypoint as part of route
7418 {
7419 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7420 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7421 pr->m_bIsBeingEdited = setBeingEdited;
7422 }
7423 m_bRouteEditing = setBeingEdited;
7424 } else // editing Mark
7425 {
7426 frp->m_bRPIsBeingEdited = setBeingEdited;
7427 m_bMarkEditing = setBeingEdited;
7428 }
7429
7430 m_pRoutePointEditTarget = frp;
7431 m_pFoundPoint = pFind;
7432 break; // out of the while(node)
7433 }
7434
7435 node = node->GetNext();
7436 } // while (node)
7437}
7438
7439void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7440 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7441 singleClickEventIsValid = false;
7442 m_DoubleClickTimer->Stop();
7443}
7444
7445bool leftIsDown;
7446
7447bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7448 if (!m_bChartDragging && !m_bDrawingRoute) {
7449 /*
7450 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7451 * mouse event coordinates are in logical pixels.
7452 */
7453 if (m_Compass && m_Compass->IsShown()) {
7454 wxRect logicalRect = m_Compass->GetLogicalRect();
7455 bool isInCompass = logicalRect.Contains(event.GetPosition());
7456 if (isInCompass) {
7457 if (m_Compass->MouseEvent(event)) {
7458 cursor_region = CENTER;
7459 if (!g_btouch) SetCanvasCursor(event);
7460 return true;
7461 }
7462 }
7463 }
7464
7465 if (m_notification_button && m_notification_button->IsShown()) {
7466 wxRect logicalRect = m_notification_button->GetLogicalRect();
7467 bool isinButton = logicalRect.Contains(event.GetPosition());
7468 if (isinButton && event.LeftDown()) {
7469 HandleNotificationMouseClick();
7470 // if (m_notification_button->MouseEvent(event)) {
7471 // return true;
7472 //}
7473 }
7474 }
7475
7476 if (MouseEventToolbar(event)) return true;
7477
7478 if (MouseEventChartBar(event)) return true;
7479
7480 if (MouseEventMUIBar(event)) return true;
7481
7482 if (MouseEventIENCBar(event)) return true;
7483 }
7484 return false;
7485}
7486
7487void ChartCanvas::HandleNotificationMouseClick() {
7488 if (!m_NotificationsList) {
7489 m_NotificationsList = new NotificationsList(this);
7490
7491 // calculate best size for Notification list
7492
7493 wxPoint ClientUpperRight = ClientToScreen(wxPoint(GetSize().x, 0));
7494 wxPoint list_bottom = ClientToScreen(wxPoint(0, GetSize().y / 2));
7495 int size_y = list_bottom.y - (ClientUpperRight.y + 5);
7496 size_y -= GetCharHeight();
7497 size_y = wxMax(size_y, 200); // ensure always big enough to see
7498
7499 m_NotificationsList->SetSize(wxSize(GetCharWidth() * 80, size_y));
7500
7501 wxPoint m_currentNLPos = ClientToScreen(wxPoint(
7502 GetSize().x / 2, m_notification_button->GetRect().y +
7503 m_notification_button->GetRect().height + 5));
7504
7505 m_NotificationsList->Move(m_currentNLPos);
7506 m_NotificationsList->Hide();
7507 }
7508
7509 if (m_NotificationsList->IsShown()) {
7510 m_NotificationsList->Hide();
7511 } else {
7512 m_NotificationsList->ReloadNotificationList();
7513 m_NotificationsList->Show();
7514 }
7515}
7516bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7517 if (!g_bShowChartBar) return false;
7518
7519 if (!m_Piano->MouseEvent(event)) return false;
7520
7521 cursor_region = CENTER;
7522 if (!g_btouch) SetCanvasCursor(event);
7523 return true;
7524}
7525
7526bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7527 if (!IsPrimaryCanvas()) return false;
7528
7529 if (g_MainToolbar) {
7530 if (!g_MainToolbar->MouseEvent(event))
7531 return false;
7532 else
7533 g_MainToolbar->RefreshToolbar();
7534 }
7535
7536 cursor_region = CENTER;
7537 if (!g_btouch) SetCanvasCursor(event);
7538 return true;
7539}
7540
7541bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7542 if (!IsPrimaryCanvas()) return false;
7543
7544 if (g_iENCToolbar) {
7545 if (!g_iENCToolbar->MouseEvent(event))
7546 return false;
7547 else {
7548 g_iENCToolbar->RefreshToolbar();
7549 return true;
7550 }
7551 }
7552 return false;
7553}
7554
7555bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7556 if (m_muiBar) {
7557 if (!m_muiBar->MouseEvent(event)) return false;
7558 }
7559
7560 cursor_region = CENTER;
7561 if (!g_btouch) SetCanvasCursor(event);
7562 if (m_muiBar)
7563 return true;
7564 else
7565 return false;
7566}
7567
7568bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7569 int x, y;
7570
7571 bool bret = false;
7572
7573 event.GetPosition(&x, &y);
7574
7575 x *= m_displayScale;
7576 y *= m_displayScale;
7577
7578 m_MouseDragging = event.Dragging();
7579
7580 // Some systems produce null drag events, where the pointer position has not
7581 // changed from the previous value. Detect this case, and abort further
7582 // processing (FS#1748)
7583#ifdef __WXMSW__
7584 if (event.Dragging()) {
7585 if ((x == mouse_x) && (y == mouse_y)) return true;
7586 }
7587#endif
7588
7589 mouse_x = x;
7590 mouse_y = y;
7591 mouse_leftisdown = event.LeftDown();
7593
7594 // Establish the event region
7595 cursor_region = CENTER;
7596
7597 int chartbar_height = GetChartbarHeight();
7598
7599 if (m_Compass && m_Compass->IsShown() &&
7600 m_Compass->GetRect().Contains(event.GetPosition())) {
7601 cursor_region = CENTER;
7602 } else if (x > xr_margin) {
7603 cursor_region = MID_RIGHT;
7604 } else if (x < xl_margin) {
7605 cursor_region = MID_LEFT;
7606 } else if (y > yb_margin - chartbar_height &&
7607 y < m_canvas_height - chartbar_height) {
7608 cursor_region = MID_TOP;
7609 } else if (y < yt_margin) {
7610 cursor_region = MID_BOT;
7611 } else {
7612 cursor_region = CENTER;
7613 }
7614
7615 if (!g_btouch) SetCanvasCursor(event);
7616
7617 // Protect from leftUp's coming from event handlers in child
7618 // windows who return focus to the canvas.
7619 leftIsDown = event.LeftDown();
7620
7621#ifndef __WXOSX__
7622 if (event.LeftDown()) {
7623 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7624 // The menu bar is temporarily visible due to alt having been pressed.
7625 // Clicking will hide it, and do nothing else.
7626 g_bTempShowMenuBar = false;
7627 parent_frame->ApplyGlobalSettings(false);
7628 return (true);
7629 }
7630 }
7631#endif
7632
7633 // Update modifiers here; some window managers never send the key event
7634 m_modkeys = 0;
7635 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7636 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7637
7638#ifdef __WXMSW__
7639 // TODO Test carefully in other platforms, remove ifdef....
7640 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7641 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7642#endif
7643
7644 event.SetEventObject(this);
7645 if (SendMouseEventToPlugins(event))
7646 return (true); // PlugIn did something, and does not want the canvas to
7647 // do anything else
7648
7649 // Capture LeftUp's and time them, unless it already came from the timer.
7650
7651 // Detect end of chart dragging
7652 if (g_btouch && m_bChartDragging && event.LeftUp()) {
7653 StartChartDragInertia();
7654 }
7655
7656 if (b_handle_dclick && event.LeftUp() && !singleClickEventIsValid) {
7657 // Ignore the second LeftUp after the DClick.
7658 if (m_DoubleClickTimer->IsRunning()) {
7659 m_DoubleClickTimer->Stop();
7660 return (true);
7661 }
7662
7663 // Save the event for later running if there is no DClick.
7664 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7665 singleClickEvent = event;
7666 singleClickEventIsValid = true;
7667 return (true);
7668 }
7669
7670 // This logic is necessary on MSW to handle the case where
7671 // a context (right-click) menu is dismissed without action
7672 // by clicking on the chart surface.
7673 // We need to avoid an unintentional pan by eating some clicks...
7674#ifdef __WXMSW__
7675 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7676 if (g_click_stop > 0) {
7677 g_click_stop--;
7678 return (true);
7679 }
7680 }
7681#endif
7682
7683 // Kick off the Rotation control timer
7684 if (GetUpMode() == COURSE_UP_MODE) {
7685 m_b_rot_hidef = false;
7686 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7687 } else
7688 pRotDefTimer->Stop();
7689
7690 // Retrigger the route leg / AIS target popup timer
7691 bool bRoll = !g_btouch;
7692#ifdef __ANDROID__
7693 bRoll = g_bRollover;
7694#endif
7695 if (bRoll) {
7696 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7697 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7698 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7699 m_RolloverPopupTimer.Start(
7700 10,
7701 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7702 else
7703 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7704 }
7705
7706 // Retrigger the cursor tracking timer
7707 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7708
7709// Show cursor position on Status Bar, if present
7710// except for GTK, under which status bar updates are very slow
7711// due to Update() call.
7712// In this case, as a workaround, update the status window
7713// after an interval timer (pCurTrackTimer) pops, which will happen
7714// whenever the mouse has stopped moving for specified interval.
7715// See the method OnCursorTrackTimerEvent()
7716#if !defined(__WXGTK__) && !defined(__WXQT__)
7717 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7718#endif
7719
7720 // Send the current cursor lat/lon to all PlugIns requesting it
7721 if (g_pi_manager) {
7722 // Occasionally, MSW will produce nonsense events on right click....
7723 // This results in an error in cursor geo position, so we skip this case
7724 if ((x >= 0) && (y >= 0))
7725 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7726 }
7727
7728 if (!g_btouch) {
7729 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7730 wxPoint p = ClientToScreen(wxPoint(x, y));
7731 }
7732 }
7733
7734 if (1 ) {
7735 // Route Creation Rubber Banding
7736 if (m_routeState >= 2) {
7737 r_rband.x = x;
7738 r_rband.y = y;
7739 m_bDrawingRoute = true;
7740
7741 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7742 Refresh(false);
7743 }
7744
7745 // Measure Tool Rubber Banding
7746 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7747 r_rband.x = x;
7748 r_rband.y = y;
7749 m_bDrawingRoute = true;
7750
7751 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7752 Refresh(false);
7753 }
7754 }
7755 return bret;
7756}
7757
7758void ChartCanvas::CallPopupMenu(int x, int y) {
7759 int mx, my;
7760 mx = x;
7761 my = y;
7762
7763 last_drag.x = mx;
7764 last_drag.y = my;
7765 if (m_routeState) { // creating route?
7766 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
7767 return;
7768 }
7769 // General Right Click
7770 // Look for selectable objects
7771 double slat, slon;
7772 slat = m_cursor_lat;
7773 slon = m_cursor_lon;
7774
7775#if defined(__WXMAC__) || defined(__ANDROID__)
7776 wxScreenDC sdc;
7777 ocpnDC dc(sdc);
7778#else
7779 wxClientDC cdc(GetParent());
7780 ocpnDC dc(cdc);
7781#endif
7782
7783 SelectItem *pFindAIS;
7784 SelectItem *pFindRP;
7785 SelectItem *pFindRouteSeg;
7786 SelectItem *pFindTrackSeg;
7787 SelectItem *pFindCurrent = NULL;
7788 SelectItem *pFindTide = NULL;
7789
7790 // Deselect any current objects
7791 if (m_pSelectedRoute) {
7792 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7793 m_pSelectedRoute->DeSelectRoute();
7794#ifdef ocpnUSE_GL
7795 if (g_bopengl && m_glcc) {
7796 InvalidateGL();
7797 Update();
7798 } else
7799#endif
7800 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7801 }
7802
7803 if (m_pFoundRoutePoint) {
7804 m_pFoundRoutePoint->m_bPtIsSelected = false;
7805 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
7806 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
7807 }
7808
7811 if (g_btouch && m_pRoutePointEditTarget) {
7812 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7813 m_pRoutePointEditTarget->m_bPtIsSelected = false;
7814 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7815 }
7816
7817 // Get all the selectable things at the cursor
7818 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7819 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7820 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7821 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7822 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7823
7824 if (m_bShowCurrent)
7825 pFindCurrent =
7826 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7827
7828 if (m_bShowTide) // look for tide stations
7829 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7830
7831 int seltype = 0;
7832
7833 // Try for AIS targets first
7834 if (pFindAIS) {
7835 m_FoundAIS_MMSI = pFindAIS->GetUserData();
7836
7837 // Make sure the target data is available
7838 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
7839 seltype |= SELTYPE_AISTARGET;
7840 }
7841
7842 // Now the various Route Parts
7843
7844 m_pFoundRoutePoint = NULL;
7845 if (pFindRP) {
7846 RoutePoint *pFirstVizPoint = NULL;
7847 RoutePoint *pFoundActiveRoutePoint = NULL;
7848 RoutePoint *pFoundVizRoutePoint = NULL;
7849 Route *pSelectedActiveRoute = NULL;
7850 Route *pSelectedVizRoute = NULL;
7851
7852 // There is at least one routepoint, so get the whole list
7853 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7854 SelectableItemList SelList =
7855 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7856 wxSelectableItemListNode *node = SelList.GetFirst();
7857 while (node) {
7858 SelectItem *pFindSel = node->GetData();
7859
7860 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7861
7862 // Get an array of all routes using this point
7863 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7864
7865 // Use route array (if any) to determine actual visibility for this point
7866 bool brp_viz = false;
7867 if (proute_array) {
7868 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7869 Route *pr = (Route *)proute_array->Item(ir);
7870 if (pr->IsVisible()) {
7871 brp_viz = true;
7872 break;
7873 }
7874 }
7875 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7876 // but still exists as a waypoint
7877 brp_viz = prp->IsVisible(); // so treat as isolated point
7878
7879 } else
7880 brp_viz = prp->IsVisible(); // isolated point
7881
7882 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7883
7884 // Use route array to choose the appropriate route
7885 // Give preference to any active route, otherwise select the first visible
7886 // route in the array for this point
7887 m_pSelectedRoute = NULL;
7888 if (proute_array) {
7889 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7890 Route *pr = (Route *)proute_array->Item(ir);
7891 if (pr->m_bRtIsActive) {
7892 pSelectedActiveRoute = pr;
7893 pFoundActiveRoutePoint = prp;
7894 break;
7895 }
7896 }
7897
7898 if (NULL == pSelectedVizRoute) {
7899 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7900 Route *pr = (Route *)proute_array->Item(ir);
7901 if (pr->IsVisible()) {
7902 pSelectedVizRoute = pr;
7903 pFoundVizRoutePoint = prp;
7904 break;
7905 }
7906 }
7907 }
7908
7909 delete proute_array;
7910 }
7911
7912 node = node->GetNext();
7913 }
7914
7915 // Now choose the "best" selections
7916 if (pFoundActiveRoutePoint) {
7917 m_pFoundRoutePoint = pFoundActiveRoutePoint;
7918 m_pSelectedRoute = pSelectedActiveRoute;
7919 } else if (pFoundVizRoutePoint) {
7920 m_pFoundRoutePoint = pFoundVizRoutePoint;
7921 m_pSelectedRoute = pSelectedVizRoute;
7922 } else
7923 // default is first visible point in list
7924 m_pFoundRoutePoint = pFirstVizPoint;
7925
7926 if (m_pSelectedRoute) {
7927 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7928 } else if (m_pFoundRoutePoint)
7929 seltype |= SELTYPE_MARKPOINT;
7930
7931 // Highlite the selected point, to verify the proper right click
7932 // selection
7933 if (m_pFoundRoutePoint) {
7934 m_pFoundRoutePoint->m_bPtIsSelected = true;
7935 wxRect wp_rect;
7936 RoutePointGui(*m_pFoundRoutePoint)
7937 .CalculateDCRect(m_dc_route, this, &wp_rect);
7938 RefreshRect(wp_rect, true);
7939 }
7940 }
7941
7942 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7943 // routes But call the popup handler with identifier appropriate to the type
7944 if (pFindRouteSeg) // there is at least one select item
7945 {
7946 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7947 SelectableItemList SelList =
7948 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7949
7950 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
7951 {
7952 // Choose the first visible route containing segment in the list
7953 wxSelectableItemListNode *node = SelList.GetFirst();
7954 while (node) {
7955 SelectItem *pFindSel = node->GetData();
7956
7957 Route *pr = (Route *)pFindSel->m_pData3;
7958 if (pr->IsVisible()) {
7959 m_pSelectedRoute = pr;
7960 break;
7961 }
7962 node = node->GetNext();
7963 }
7964 }
7965
7966 if (m_pSelectedRoute) {
7967 if (NULL == m_pFoundRoutePoint)
7968 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7969
7970 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7971 if (m_pSelectedRoute->m_bRtIsSelected) {
7972#ifdef ocpnUSE_GL
7973 if (g_bopengl && m_glcc) {
7974 InvalidateGL();
7975 Update();
7976 } else
7977#endif
7978 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7979 }
7980
7981 seltype |= SELTYPE_ROUTESEGMENT;
7982 }
7983 }
7984
7985 if (pFindTrackSeg) {
7986 m_pSelectedTrack = NULL;
7987 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7988 SelectableItemList SelList =
7989 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7990
7991 // Choose the first visible track containing segment in the list
7992 wxSelectableItemListNode *node = SelList.GetFirst();
7993 while (node) {
7994 SelectItem *pFindSel = node->GetData();
7995
7996 Track *pt = (Track *)pFindSel->m_pData3;
7997 if (pt->IsVisible()) {
7998 m_pSelectedTrack = pt;
7999 break;
8000 }
8001 node = node->GetNext();
8002 }
8003
8004 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8005 }
8006
8007 bool bseltc = false;
8008 // if(0 == seltype)
8009 {
8010 if (pFindCurrent) {
8011 // There may be multiple current entries at the same point.
8012 // For example, there often is a current substation (with directions
8013 // specified) co-located with its master. We want to select the
8014 // substation, so that the direction will be properly indicated on the
8015 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8016 // substation)
8017 IDX_entry *pIDX_best_candidate;
8018
8019 SelectItem *pFind = NULL;
8020 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8021 SelectableItemList SelList = pSelectTC->FindSelectionList(
8022 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_CURRENTPOINT);
8023
8024 // Default is first entry
8025 wxSelectableItemListNode *node = SelList.GetFirst();
8026 pFind = node->GetData();
8027 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8028
8029 if (SelList.GetCount() > 1) {
8030 node = node->GetNext();
8031 while (node) {
8032 pFind = node->GetData();
8033 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8034 if (pIDX_candidate->IDX_type == 'c') {
8035 pIDX_best_candidate = pIDX_candidate;
8036 break;
8037 }
8038
8039 node = node->GetNext();
8040 } // while (node)
8041 } else {
8042 wxSelectableItemListNode *node = SelList.GetFirst();
8043 pFind = node->GetData();
8044 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8045 }
8046
8047 m_pIDXCandidate = pIDX_best_candidate;
8048
8049 if (0 == seltype) {
8050 DrawTCWindow(x, y, (void *)pIDX_best_candidate);
8051 Refresh(false);
8052 bseltc = true;
8053 } else
8054 seltype |= SELTYPE_CURRENTPOINT;
8055 }
8056
8057 else if (pFindTide) {
8058 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8059
8060 if (0 == seltype) {
8061 DrawTCWindow(x, y, (void *)pFindTide->m_pData1);
8062 Refresh(false);
8063 bseltc = true;
8064 } else
8065 seltype |= SELTYPE_TIDEPOINT;
8066 }
8067 }
8068
8069 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8070
8071 if (!bseltc) {
8072 InvokeCanvasMenu(x, y, seltype);
8073
8074 // Clean up if not deleted in InvokeCanvasMenu
8075 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8076 m_pSelectedRoute->m_bRtIsSelected = false;
8077 }
8078
8079 m_pSelectedRoute = NULL;
8080
8081 if (m_pFoundRoutePoint) {
8082 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8083 m_pFoundRoutePoint->m_bPtIsSelected = false;
8084 }
8085 m_pFoundRoutePoint = NULL;
8086
8087 Refresh(true);
8088 }
8089
8090 // Seth: Is this refresh needed?
8091 Refresh(false); // needed for MSW, not GTK Why??
8092}
8093bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8094 // For now just bail out completely if the point clicked is not on the chart
8095 if (std::isnan(m_cursor_lat)) return false;
8096
8097 // Mouse Clicks
8098 bool ret = false; // return true if processed
8099
8100 int x, y, mx, my;
8101 event.GetPosition(&x, &y);
8102 mx = x;
8103 my = y;
8104
8105 // Calculate meaningful SelectRadius
8106 float SelectRadius;
8107 SelectRadius = g_Platform->GetSelectRadiusPix() /
8108 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8109
8111 // We start with Double Click processing. The first left click just starts a
8112 // timer and is remembered, then we actually do something if there is a
8113 // LeftDClick. If there is, the two single clicks are ignored.
8114
8115 if (event.LeftDClick() && (cursor_region == CENTER)) {
8116 m_DoubleClickTimer->Start();
8117 singleClickEventIsValid = false;
8118
8119 double zlat, zlon;
8120 GetCanvasPixPoint(x * g_current_monitor_dip_px_ratio,
8121 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8122
8123 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8124 if (m_bShowAIS) {
8125 SelectItem *pFindAIS;
8126 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8127
8128 if (pFindAIS) {
8129 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8130 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8131 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8132 }
8133 return true;
8134 }
8135 }
8136
8137 SelectableItemList rpSelList =
8138 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8139 wxSelectableItemListNode *node = rpSelList.GetFirst();
8140 bool b_onRPtarget = false;
8141 while (node) {
8142 SelectItem *pFind = node->GetData();
8143 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8144 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8145 b_onRPtarget = true;
8146 break;
8147 }
8148 node = node->GetNext();
8149 }
8150
8151 // Double tap with selected RoutePoint or Mark
8152
8153 if (m_pRoutePointEditTarget) {
8154 if (b_onRPtarget) {
8155 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8156 return true;
8157 } else {
8158 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8159 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8160 if (g_btouch)
8161 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8162 wxRect wp_rect;
8163 RoutePointGui(*m_pRoutePointEditTarget)
8164 .CalculateDCRect(m_dc_route, this, &wp_rect);
8165 m_pRoutePointEditTarget = NULL; // cancel selection
8166 RefreshRect(wp_rect, true);
8167 return true;
8168 }
8169 } else {
8170 node = rpSelList.GetFirst();
8171 if (node) {
8172 SelectItem *pFind = node->GetData();
8173 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8174 if (frp) {
8175 wxArrayPtrVoid *proute_array =
8176 g_pRouteMan->GetRouteArrayContaining(frp);
8177
8178 // Use route array (if any) to determine actual visibility for this
8179 // point
8180 bool brp_viz = false;
8181 if (proute_array) {
8182 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8183 Route *pr = (Route *)proute_array->Item(ir);
8184 if (pr->IsVisible()) {
8185 brp_viz = true;
8186 break;
8187 }
8188 }
8189 delete proute_array;
8190 if (!brp_viz &&
8191 frp->IsShared()) // is not visible as part of route, but still
8192 // exists as a waypoint
8193 brp_viz = frp->IsVisible(); // so treat as isolated point
8194 } else
8195 brp_viz = frp->IsVisible(); // isolated point
8196
8197 if (brp_viz) {
8198 ShowMarkPropertiesDialog(frp);
8199 return true;
8200 }
8201 }
8202 }
8203 }
8204
8205 SelectItem *cursorItem;
8206 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8207
8208 if (cursorItem) {
8209 Route *pr = (Route *)cursorItem->m_pData3;
8210 if (pr->IsVisible()) {
8211 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8212 return true;
8213 }
8214 }
8215
8216 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8217
8218 if (cursorItem) {
8219 Track *pt = (Track *)cursorItem->m_pData3;
8220 if (pt->IsVisible()) {
8221 ShowTrackPropertiesDialog(pt);
8222 return true;
8223 }
8224 }
8225
8226 // Found no object to act on, so show chart info.
8227
8228 ShowObjectQueryWindow(x, y, zlat, zlon);
8229 return true;
8230 }
8231
8233 if (event.LeftDown()) {
8234 // This really should not be needed, but....
8235 // on Windows, when using wxAUIManager, sometimes the focus is lost
8236 // when clicking into another pane, e.g.the AIS target list, and then back
8237 // to this pane. Oddly, some mouse events are not lost, however. Like this
8238 // one....
8239 SetFocus();
8240
8241 last_drag.x = mx;
8242 last_drag.y = my;
8243 leftIsDown = true;
8244
8245 if (!g_btouch) {
8246 if (m_routeState) // creating route?
8247 {
8248 double rlat, rlon;
8249 bool appending = false;
8250 bool inserting = false;
8251 Route *tail = 0;
8252
8253 SetCursor(*pCursorPencil);
8254 rlat = m_cursor_lat;
8255 rlon = m_cursor_lon;
8256
8257 m_bRouteEditing = true;
8258
8259 if (m_routeState == 1) {
8260 m_pMouseRoute = new Route();
8261 pRouteList->Append(m_pMouseRoute);
8262 r_rband.x = x;
8263 r_rband.y = y;
8264 }
8265
8266 // Check to see if there is a nearby point which may be reused
8267 RoutePoint *pMousePoint = NULL;
8268
8269 // Calculate meaningful SelectRadius
8270 double nearby_radius_meters =
8271 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8272
8273 RoutePoint *pNearbyPoint =
8274 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8275 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8276 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8277 wxArrayPtrVoid *proute_array =
8278 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8279
8280 // Use route array (if any) to determine actual visibility for this
8281 // point
8282 bool brp_viz = false;
8283 if (proute_array) {
8284 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8285 Route *pr = (Route *)proute_array->Item(ir);
8286 if (pr->IsVisible()) {
8287 brp_viz = true;
8288 break;
8289 }
8290 }
8291 delete proute_array;
8292 if (!brp_viz &&
8293 pNearbyPoint->IsShared()) // is not visible as part of route,
8294 // but still exists as a waypoint
8295 brp_viz =
8296 pNearbyPoint->IsVisible(); // so treat as isolated point
8297 } else
8298 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8299
8300 if (brp_viz) {
8301 wxString msg = _("Use nearby waypoint?");
8302 // Don't add a mark without name to the route. Name it if needed
8303 const bool noname(pNearbyPoint->GetName() == "");
8304 if (noname) {
8305 msg =
8306 _("Use nearby nameless waypoint and name it M with"
8307 " a unique number?");
8308 }
8309 // Avoid route finish on focus change for message dialog
8310 m_FinishRouteOnKillFocus = false;
8311 int dlg_return =
8312 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8313 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8314 m_FinishRouteOnKillFocus = true;
8315 if (dlg_return == wxID_YES) {
8316 if (noname) {
8317 if (m_pMouseRoute) {
8318 int last_wp_num = m_pMouseRoute->GetnPoints();
8319 // AP-ECRMB will truncate to 6 characters
8320 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8321 wxString wp_name = wxString::Format(
8322 "M%002i-%s", last_wp_num + 1, guid_short);
8323 pNearbyPoint->SetName(wp_name);
8324 } else
8325 pNearbyPoint->SetName("WPXX");
8326 }
8327 pMousePoint = pNearbyPoint;
8328
8329 // Using existing waypoint, so nothing to delete for undo.
8330 if (m_routeState > 1)
8331 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8332 Undo_HasParent, NULL);
8333
8334 tail =
8335 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8336 bool procede = false;
8337 if (tail) {
8338 procede = true;
8339 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8340 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8341 procede = false;
8342 }
8343
8344 if (procede) {
8345 int dlg_return;
8346 m_FinishRouteOnKillFocus = false;
8347 if (m_routeState ==
8348 1) { // first point in new route, preceeding route to be
8349 // added? Not touch case
8350
8351 wxString dmsg =
8352 _("Insert first part of this route in the new route?");
8353 if (tail->GetIndexOf(pMousePoint) ==
8354 tail->GetnPoints()) // Starting on last point of another
8355 // route?
8356 dmsg = _("Insert this route in the new route?");
8357
8358 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8359 dlg_return = OCPNMessageBox(
8360 this, dmsg, _("OpenCPN Route Create"),
8361 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8362 m_FinishRouteOnKillFocus = true;
8363
8364 if (dlg_return == wxID_YES) {
8365 inserting = true; // part of the other route will be
8366 // preceeding the new route
8367 }
8368 }
8369 } else {
8370 wxString dmsg =
8371 _("Append last part of this route to the new route?");
8372 if (tail->GetIndexOf(pMousePoint) == 1)
8373 dmsg = _(
8374 "Append this route to the new route?"); // Picking the
8375 // first point
8376 // of another
8377 // route?
8378
8379 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8380 dlg_return = OCPNMessageBox(
8381 this, dmsg, _("OpenCPN Route Create"),
8382 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8383 m_FinishRouteOnKillFocus = true;
8384
8385 if (dlg_return == wxID_YES) {
8386 appending = true; // part of the other route will be
8387 // appended to the new route
8388 }
8389 }
8390 }
8391 }
8392
8393 // check all other routes to see if this point appears in any
8394 // other route If it appears in NO other route, then it should e
8395 // considered an isolated mark
8396 if (!FindRouteContainingWaypoint(pMousePoint))
8397 pMousePoint->SetShared(true);
8398 }
8399 }
8400 }
8401
8402 if (NULL == pMousePoint) { // need a new point
8403 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8404 _T(""), wxEmptyString);
8405 pMousePoint->SetNameShown(false);
8406
8407 pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8408 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8409
8410 if (m_routeState > 1)
8411 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8412 Undo_IsOrphanded, NULL);
8413 }
8414
8415 if (m_pMouseRoute) {
8416 if (m_routeState == 1) {
8417 // First point in the route.
8418 m_pMouseRoute->AddPoint(pMousePoint);
8419 } else {
8420 if (m_pMouseRoute->m_NextLegGreatCircle) {
8421 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8422 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8423 &rhumbBearing, &rhumbDist);
8424 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8425 rlat, &gcDist, &gcBearing, NULL);
8426 double gcDistNM = gcDist / 1852.0;
8427
8428 // Empirically found expression to get reasonable route segments.
8429 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8430 pow(rhumbDist - gcDistNM - 1, 0.5);
8431
8432 wxString msg;
8433 msg << _("For this leg the Great Circle route is ")
8434 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8435 << _(" shorter than rhumbline.\n\n")
8436 << _("Would you like include the Great Circle routing points "
8437 "for this leg?");
8438
8439 m_FinishRouteOnKillFocus = false;
8440 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8441 // does not fully capture mouse
8442
8443 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8444 wxYES_NO | wxNO_DEFAULT);
8445
8446 m_disable_edge_pan = false;
8447 m_FinishRouteOnKillFocus = true;
8448
8449 if (answer == wxID_YES) {
8450 RoutePoint *gcPoint;
8451 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8452 wxRealPoint gcCoord;
8453
8454 for (int i = 1; i <= segmentCount; i++) {
8455 double fraction = (double)i * (1.0 / (double)segmentCount);
8456 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8457 gcDist * fraction, gcBearing,
8458 &gcCoord.x, &gcCoord.y, NULL);
8459
8460 if (i < segmentCount) {
8461 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
8462 _T(""), wxEmptyString);
8463 gcPoint->SetNameShown(false);
8464 pConfig->AddNewWayPoint(gcPoint, -1);
8465 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8466 gcPoint);
8467 } else {
8468 gcPoint = pMousePoint; // Last point, previously exsisting!
8469 }
8470
8471 m_pMouseRoute->AddPoint(gcPoint);
8472 pSelect->AddSelectableRouteSegment(
8473 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8474 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8475 prevGcPoint = gcPoint;
8476 }
8477
8478 undo->CancelUndoableAction(true);
8479
8480 } else {
8481 m_pMouseRoute->AddPoint(pMousePoint);
8482 pSelect->AddSelectableRouteSegment(
8483 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8484 pMousePoint, m_pMouseRoute);
8485 undo->AfterUndoableAction(m_pMouseRoute);
8486 }
8487 } else {
8488 // Ordinary rhumblinesegment.
8489 m_pMouseRoute->AddPoint(pMousePoint);
8490 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8491 rlon, m_prev_pMousePoint,
8492 pMousePoint, m_pMouseRoute);
8493 undo->AfterUndoableAction(m_pMouseRoute);
8494 }
8495 }
8496 }
8497 m_prev_rlat = rlat;
8498 m_prev_rlon = rlon;
8499 m_prev_pMousePoint = pMousePoint;
8500 if (m_pMouseRoute)
8501 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8502
8503 m_routeState++;
8504
8505 if (appending ||
8506 inserting) { // Appending a route or making a new route
8507 int connect = tail->GetIndexOf(pMousePoint);
8508 if (connect == 1) {
8509 inserting = false; // there is nothing to insert
8510 appending = true; // so append
8511 }
8512 int length = tail->GetnPoints();
8513
8514 int i;
8515 int start, stop;
8516 if (appending) {
8517 start = connect + 1;
8518 stop = length;
8519 } else { // inserting
8520 start = 1;
8521 stop = connect;
8522 m_pMouseRoute->RemovePoint(
8523 m_pMouseRoute
8524 ->GetLastPoint()); // Remove the first and only point
8525 }
8526 for (i = start; i <= stop; i++) {
8527 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8528 if (m_pMouseRoute)
8529 m_pMouseRoute->m_lastMousePointIndex =
8530 m_pMouseRoute->GetnPoints();
8531 m_routeState++;
8532 gFrame->RefreshAllCanvas();
8533 ret = true;
8534 }
8535 m_prev_rlat =
8536 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8537 m_prev_rlon =
8538 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8539 m_pMouseRoute->FinalizeForRendering();
8540 }
8541 gFrame->RefreshAllCanvas();
8542 ret = true;
8543 }
8544
8545 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8546 {
8547 SetCursor(*pCursorPencil);
8548
8549 if (!m_pMeasureRoute) {
8550 m_pMeasureRoute = new Route();
8551 pRouteList->Append(m_pMeasureRoute);
8552 }
8553
8554 if (m_nMeasureState == 1) {
8555 r_rband.x = x;
8556 r_rband.y = y;
8557 }
8558
8559 RoutePoint *pMousePoint = new RoutePoint(m_cursor_lat, m_cursor_lon,
8560 wxString(_T ( "circle" )),
8561 wxEmptyString, wxEmptyString);
8562 pMousePoint->m_bShowName = false;
8563 pMousePoint->SetShowWaypointRangeRings(false);
8564
8565 m_pMeasureRoute->AddPoint(pMousePoint);
8566
8567 m_prev_rlat = m_cursor_lat;
8568 m_prev_rlon = m_cursor_lon;
8569 m_prev_pMousePoint = pMousePoint;
8570 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8571
8572 m_nMeasureState++;
8573 gFrame->RefreshAllCanvas();
8574 ret = true;
8575 }
8576
8577 else {
8578 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8579 }
8580 } // !g_btouch
8581 else { // g_btouch
8582
8583 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8584 // if near screen edge, pan with injection
8585 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8586 // return;
8587 // }
8588 }
8589 }
8590
8591 if (ret) return true;
8592 }
8593
8594 if (event.Dragging()) {
8595 // in touch screen mode ensure the finger/cursor is on the selected point's
8596 // radius to allow dragging
8597 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8598 if (g_btouch) {
8599 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8600 SelectItem *pFind = NULL;
8601 SelectableItemList SelList = pSelect->FindSelectionList(
8602 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8603 wxSelectableItemListNode *node = SelList.GetFirst();
8604 while (node) {
8605 pFind = node->GetData();
8606 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8607 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8608 node = node->GetNext();
8609 }
8610 }
8611
8612 // Check for use of dragHandle
8613 if (m_pRoutePointEditTarget &&
8614 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8615 SelectItem *pFind = NULL;
8616 SelectableItemList SelList = pSelect->FindSelectionList(
8617 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8618 wxSelectableItemListNode *node = SelList.GetFirst();
8619 while (node) {
8620 pFind = node->GetData();
8621 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8622 if (m_pRoutePointEditTarget == frp) {
8623 m_bIsInRadius = true;
8624 break;
8625 }
8626 node = node->GetNext();
8627 }
8628
8629 if (!m_dragoffsetSet) {
8630 RoutePointGui(*m_pRoutePointEditTarget)
8631 .PresetDragOffset(this, mouse_x, mouse_y);
8632 m_dragoffsetSet = true;
8633 }
8634 }
8635 }
8636
8637 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8638 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8639
8640 if (NULL == g_pMarkInfoDialog) {
8641 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8642 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8643 DraggingAllowed = false;
8644
8645 if (m_pRoutePointEditTarget &&
8646 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8647 DraggingAllowed = false;
8648
8649 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8650
8651 if (DraggingAllowed) {
8652 if (!undo->InUndoableAction()) {
8653 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8654 Undo_NeedsCopy, m_pFoundPoint);
8655 }
8656
8657 // Get the update rectangle for the union of the un-edited routes
8658 wxRect pre_rect;
8659
8660 if (!g_bopengl && m_pEditRouteArray) {
8661 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8662 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8663 // Need to validate route pointer
8664 // Route may be gone due to drgging close to ownship with
8665 // "Delete On Arrival" state set, as in the case of
8666 // navigating to an isolated waypoint on a temporary route
8667 if (g_pRouteMan->IsRouteValid(pr)) {
8668 wxRect route_rect;
8669 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8670 pre_rect.Union(route_rect);
8671 }
8672 }
8673 }
8674
8675 double new_cursor_lat = m_cursor_lat;
8676 double new_cursor_lon = m_cursor_lon;
8677
8678 if (CheckEdgePan(x, y, true, 5, 2))
8679 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8680
8681 // update the point itself
8682 if (g_btouch) {
8683 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8684 // new_cursor_lat, new_cursor_lon);
8685 RoutePointGui(*m_pRoutePointEditTarget)
8686 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8687 // update the Drag Handle entry in the pSelect list
8688 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8689 m_pRoutePointEditTarget,
8690 SELTYPE_DRAGHANDLE);
8691 m_pFoundPoint->m_slat =
8692 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8693 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8694 } else {
8695 m_pRoutePointEditTarget->m_lat =
8696 new_cursor_lat; // update the RoutePoint entry
8697 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8698 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8699 m_pFoundPoint->m_slat =
8700 new_cursor_lat; // update the SelectList entry
8701 m_pFoundPoint->m_slon = new_cursor_lon;
8702 }
8703
8704 // Update the MarkProperties Dialog, if currently shown
8705 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8706 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8707 g_pMarkInfoDialog->UpdateProperties(true);
8708 }
8709
8710 if (g_bopengl) {
8711 // InvalidateGL();
8712 Refresh(false);
8713 } else {
8714 // Get the update rectangle for the edited route
8715 wxRect post_rect;
8716
8717 if (m_pEditRouteArray) {
8718 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8719 ir++) {
8720 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8721 if (g_pRouteMan->IsRouteValid(pr)) {
8722 wxRect route_rect;
8723 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8724 post_rect.Union(route_rect);
8725 }
8726 }
8727 }
8728
8729 // Invalidate the union region
8730 pre_rect.Union(post_rect);
8731 RefreshRect(pre_rect, false);
8732 }
8733 gFrame->RefreshCanvasOther(this);
8734 m_bRoutePoinDragging = true;
8735 }
8736 ret = true;
8737 } // if Route Editing
8738
8739 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8740 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8741
8742 if (NULL == g_pMarkInfoDialog) {
8743 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8744 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8745 DraggingAllowed = false;
8746
8747 if (m_pRoutePointEditTarget &&
8748 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8749 DraggingAllowed = false;
8750
8751 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8752
8753 if (DraggingAllowed) {
8754 if (!undo->InUndoableAction()) {
8755 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8756 Undo_NeedsCopy, m_pFoundPoint);
8757 }
8758
8759 // The mark may be an anchorwatch
8760 double lpp1 = 0.;
8761 double lpp2 = 0.;
8762 double lppmax;
8763
8764 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8765 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8766 }
8767 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8768 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8769 }
8770 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8771
8772 // Get the update rectangle for the un-edited mark
8773 wxRect pre_rect;
8774 if (!g_bopengl) {
8775 RoutePointGui(*m_pRoutePointEditTarget)
8776 .CalculateDCRect(m_dc_route, this, &pre_rect);
8777 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8778 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8779 (int)(lppmax - (pre_rect.height / 2)));
8780 }
8781
8782 // update the point itself
8783 if (g_btouch) {
8784 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8785 // m_cursor_lat, m_cursor_lon);
8786 RoutePointGui(*m_pRoutePointEditTarget)
8787 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8788 // update the Drag Handle entry in the pSelect list
8789 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
8790 m_pRoutePointEditTarget,
8791 SELTYPE_DRAGHANDLE);
8792 m_pFoundPoint->m_slat =
8793 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8794 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8795 } else {
8796 m_pRoutePointEditTarget->m_lat =
8797 m_cursor_lat; // update the RoutePoint entry
8798 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
8799 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8800 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
8801 m_pFoundPoint->m_slon = m_cursor_lon;
8802 }
8803
8804 // Update the MarkProperties Dialog, if currently shown
8805 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8806 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8807 g_pMarkInfoDialog->UpdateProperties(true);
8808 }
8809
8810 // Invalidate the union region
8811 if (g_bopengl) {
8812 if (!g_btouch) InvalidateGL();
8813 Refresh(false);
8814 } else {
8815 // Get the update rectangle for the edited mark
8816 wxRect post_rect;
8817 RoutePointGui(*m_pRoutePointEditTarget)
8818 .CalculateDCRect(m_dc_route, this, &post_rect);
8819 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
8820 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
8821 (int)(lppmax - (post_rect.height / 2)));
8822
8823 // Invalidate the union region
8824 pre_rect.Union(post_rect);
8825 RefreshRect(pre_rect, false);
8826 }
8827 gFrame->RefreshCanvasOther(this);
8828 m_bRoutePoinDragging = true;
8829 }
8830 ret = true;
8831 }
8832
8833 if (ret) return true;
8834 } // dragging
8835
8836 if (event.LeftUp()) {
8837 bool b_startedit_route = false;
8838 m_dragoffsetSet = false;
8839
8840 if (g_btouch) {
8841 m_bChartDragging = false;
8842 m_bIsInRadius = false;
8843
8844 if (m_routeState) // creating route?
8845 {
8846 if (m_bedge_pan) {
8847 m_bedge_pan = false;
8848 return false;
8849 }
8850
8851 double rlat, rlon;
8852 bool appending = false;
8853 bool inserting = false;
8854 Route *tail = 0;
8855
8856 rlat = m_cursor_lat;
8857 rlon = m_cursor_lon;
8858
8859 if (m_pRoutePointEditTarget) {
8860 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8861 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8862 if (!g_bopengl) {
8863 wxRect wp_rect;
8864 RoutePointGui(*m_pRoutePointEditTarget)
8865 .CalculateDCRect(m_dc_route, this, &wp_rect);
8866 RefreshRect(wp_rect, true);
8867 }
8868 m_pRoutePointEditTarget = NULL;
8869 }
8870 m_bRouteEditing = true;
8871
8872 if (m_routeState == 1) {
8873 m_pMouseRoute = new Route();
8874 m_pMouseRoute->SetHiLite(50);
8875 pRouteList->Append(m_pMouseRoute);
8876 r_rband.x = x;
8877 r_rband.y = y;
8878 }
8879
8880 // Check to see if there is a nearby point which may be reused
8881 RoutePoint *pMousePoint = NULL;
8882
8883 // Calculate meaningful SelectRadius
8884 double nearby_radius_meters =
8885 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8886
8887 RoutePoint *pNearbyPoint =
8888 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8889 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8890 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8891 int dlg_return;
8892#ifndef __WXOSX__
8893 m_FinishRouteOnKillFocus =
8894 false; // Avoid route finish on focus change for message dialog
8895 dlg_return = OCPNMessageBox(
8896 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
8897 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8898 m_FinishRouteOnKillFocus = true;
8899#else
8900 dlg_return = wxID_YES;
8901#endif
8902 if (dlg_return == wxID_YES) {
8903 pMousePoint = pNearbyPoint;
8904
8905 // Using existing waypoint, so nothing to delete for undo.
8906 if (m_routeState > 1)
8907 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8908 Undo_HasParent, NULL);
8909 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8910
8911 bool procede = false;
8912 if (tail) {
8913 procede = true;
8914 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8915 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8916 procede = false;
8917 }
8918
8919 if (procede) {
8920 int dlg_return;
8921 m_FinishRouteOnKillFocus = false;
8922 if (m_routeState == 1) { // first point in new route, preceeding
8923 // route to be added? touch case
8924
8925 wxString dmsg =
8926 _("Insert first part of this route in the new route?");
8927 if (tail->GetIndexOf(pMousePoint) ==
8928 tail->GetnPoints()) // Starting on last point of another
8929 // route?
8930 dmsg = _("Insert this route in the new route?");
8931
8932 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8933 dlg_return =
8934 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8935 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8936 m_FinishRouteOnKillFocus = true;
8937
8938 if (dlg_return == wxID_YES) {
8939 inserting = true; // part of the other route will be
8940 // preceeding the new route
8941 }
8942 }
8943 } else {
8944 wxString dmsg =
8945 _("Append last part of this route to the new route?");
8946 if (tail->GetIndexOf(pMousePoint) == 1)
8947 dmsg = _(
8948 "Append this route to the new route?"); // Picking the
8949 // first point of
8950 // another route?
8951
8952 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8953 dlg_return =
8954 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8955 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8956 m_FinishRouteOnKillFocus = true;
8957
8958 if (dlg_return == wxID_YES) {
8959 appending = true; // part of the other route will be
8960 // appended to the new route
8961 }
8962 }
8963 }
8964 }
8965
8966 // check all other routes to see if this point appears in any other
8967 // route If it appears in NO other route, then it should e
8968 // considered an isolated mark
8969 if (!FindRouteContainingWaypoint(pMousePoint))
8970 pMousePoint->SetShared(true);
8971 }
8972 }
8973
8974 if (NULL == pMousePoint) { // need a new point
8975 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8976 _T(""), wxEmptyString);
8977 pMousePoint->SetNameShown(false);
8978
8979 pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8980 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8981
8982 if (m_routeState > 1)
8983 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8984 Undo_IsOrphanded, NULL);
8985 }
8986
8987 if (m_routeState == 1) {
8988 // First point in the route.
8989 m_pMouseRoute->AddPoint(pMousePoint);
8990 } else {
8991 if (m_pMouseRoute->m_NextLegGreatCircle) {
8992 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8993 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8994 &rhumbBearing, &rhumbDist);
8995 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
8996 &gcDist, &gcBearing, NULL);
8997 double gcDistNM = gcDist / 1852.0;
8998
8999 // Empirically found expression to get reasonable route segments.
9000 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9001 pow(rhumbDist - gcDistNM - 1, 0.5);
9002
9003 wxString msg;
9004 msg << _("For this leg the Great Circle route is ")
9005 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9006 << _(" shorter than rhumbline.\n\n")
9007 << _("Would you like include the Great Circle routing points "
9008 "for this leg?");
9009
9010#ifndef __WXOSX__
9011 m_FinishRouteOnKillFocus = false;
9012 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9013 wxYES_NO | wxNO_DEFAULT);
9014 m_FinishRouteOnKillFocus = true;
9015#else
9016 int answer = wxID_NO;
9017#endif
9018
9019 if (answer == wxID_YES) {
9020 RoutePoint *gcPoint;
9021 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9022 wxRealPoint gcCoord;
9023
9024 for (int i = 1; i <= segmentCount; i++) {
9025 double fraction = (double)i * (1.0 / (double)segmentCount);
9026 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9027 gcDist * fraction, gcBearing,
9028 &gcCoord.x, &gcCoord.y, NULL);
9029
9030 if (i < segmentCount) {
9031 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
9032 _T(""), wxEmptyString);
9033 gcPoint->SetNameShown(false);
9034 pConfig->AddNewWayPoint(gcPoint, -1);
9035 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9036 gcPoint);
9037 } else {
9038 gcPoint = pMousePoint; // Last point, previously exsisting!
9039 }
9040
9041 m_pMouseRoute->AddPoint(gcPoint);
9042 pSelect->AddSelectableRouteSegment(
9043 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9044 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9045 prevGcPoint = gcPoint;
9046 }
9047
9048 undo->CancelUndoableAction(true);
9049
9050 } else {
9051 m_pMouseRoute->AddPoint(pMousePoint);
9052 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9053 rlon, m_prev_pMousePoint,
9054 pMousePoint, m_pMouseRoute);
9055 undo->AfterUndoableAction(m_pMouseRoute);
9056 }
9057 } else {
9058 // Ordinary rhumblinesegment.
9059 m_pMouseRoute->AddPoint(pMousePoint);
9060 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9061 rlon, m_prev_pMousePoint,
9062 pMousePoint, m_pMouseRoute);
9063 undo->AfterUndoableAction(m_pMouseRoute);
9064 }
9065 }
9066
9067 m_prev_rlat = rlat;
9068 m_prev_rlon = rlon;
9069 m_prev_pMousePoint = pMousePoint;
9070 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9071
9072 m_routeState++;
9073
9074 if (appending ||
9075 inserting) { // Appending a route or making a new route
9076 int connect = tail->GetIndexOf(pMousePoint);
9077 if (connect == 1) {
9078 inserting = false; // there is nothing to insert
9079 appending = true; // so append
9080 }
9081 int length = tail->GetnPoints();
9082
9083 int i;
9084 int start, stop;
9085 if (appending) {
9086 start = connect + 1;
9087 stop = length;
9088 } else { // inserting
9089 start = 1;
9090 stop = connect;
9091 m_pMouseRoute->RemovePoint(
9092 m_pMouseRoute
9093 ->GetLastPoint()); // Remove the first and only point
9094 }
9095 for (i = start; i <= stop; i++) {
9096 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9097 if (m_pMouseRoute)
9098 m_pMouseRoute->m_lastMousePointIndex =
9099 m_pMouseRoute->GetnPoints();
9100 m_routeState++;
9101 gFrame->RefreshAllCanvas();
9102 ret = true;
9103 }
9104 m_prev_rlat =
9105 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9106 m_prev_rlon =
9107 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9108 m_pMouseRoute->FinalizeForRendering();
9109 }
9110
9111 Refresh(true);
9112 ret = true;
9113 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9114 {
9115 if (m_bedge_pan) {
9116 m_bedge_pan = false;
9117 return false;
9118 }
9119
9120 if (m_nMeasureState == 1) {
9121 m_pMeasureRoute = new Route();
9122 pRouteList->Append(m_pMeasureRoute);
9123 r_rband.x = x;
9124 r_rband.y = y;
9125 }
9126
9127 if (m_pMeasureRoute) {
9128 RoutePoint *pMousePoint = new RoutePoint(
9129 m_cursor_lat, m_cursor_lon, wxString(_T ( "circle" )),
9130 wxEmptyString, wxEmptyString);
9131 pMousePoint->m_bShowName = false;
9132
9133 m_pMeasureRoute->AddPoint(pMousePoint);
9134
9135 m_prev_rlat = m_cursor_lat;
9136 m_prev_rlon = m_cursor_lon;
9137 m_prev_pMousePoint = pMousePoint;
9138 m_pMeasureRoute->m_lastMousePointIndex =
9139 m_pMeasureRoute->GetnPoints();
9140
9141 m_nMeasureState++;
9142 } else {
9143 CancelMeasureRoute();
9144 }
9145
9146 Refresh(true);
9147 ret = true;
9148 } else {
9149 bool bSelectAllowed = true;
9150 if (NULL == g_pMarkInfoDialog) {
9151 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9152 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9153 bSelectAllowed = false;
9154
9155 /*if this left up happens at the end of a route point dragging and if
9156 the cursor/thumb is on the draghandle icon, not on the point iself a new
9157 selection will select nothing and the drag will never be ended, so the
9158 legs around this point never selectable. At this step we don't need a
9159 new selection, just keep the previoulsly selected and dragged point */
9160 if (m_bRoutePoinDragging) bSelectAllowed = false;
9161
9162 if (bSelectAllowed) {
9163 bool b_was_editing_mark = m_bMarkEditing;
9164 bool b_was_editing_route = m_bRouteEditing;
9165 FindRoutePointsAtCursor(SelectRadius,
9166 true); // Possibly selecting a point in a
9167 // route for later dragging
9168
9169 /*route and a mark points in layer can't be dragged so should't be
9170 * selected and no draghandle icon*/
9171 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9172 m_pRoutePointEditTarget = NULL;
9173
9174 if (!b_was_editing_route) {
9175 if (m_pEditRouteArray) {
9176 b_startedit_route = true;
9177
9178 // Hide the track and route rollover during route point edit, not
9179 // needed, and may be confusing
9180 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9181 m_pTrackRolloverWin->IsActive(false);
9182 }
9183 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9184 m_pRouteRolloverWin->IsActive(false);
9185 }
9186
9187 wxRect pre_rect;
9188 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9189 ir++) {
9190 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9191 // Need to validate route pointer
9192 // Route may be gone due to drgging close to ownship with
9193 // "Delete On Arrival" state set, as in the case of
9194 // navigating to an isolated waypoint on a temporary route
9195 if (g_pRouteMan->IsRouteValid(pr)) {
9196 // pr->SetHiLite(50);
9197 wxRect route_rect;
9198 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9199 pre_rect.Union(route_rect);
9200 }
9201 }
9202 RefreshRect(pre_rect, true);
9203 }
9204 } else {
9205 b_startedit_route = false;
9206 }
9207
9208 // Mark editing
9209 if (m_pRoutePointEditTarget) {
9210 if (b_was_editing_mark ||
9211 b_was_editing_route) { // kill previous hilight
9212 if (m_lastRoutePointEditTarget) {
9213 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9214 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9215 RoutePointGui(*m_lastRoutePointEditTarget)
9216 .EnableDragHandle(false);
9217 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9218 SELTYPE_DRAGHANDLE);
9219 }
9220 }
9221
9222 if (m_pRoutePointEditTarget) {
9223 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9224 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9225 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9226 wxPoint2DDouble dragHandlePoint =
9227 RoutePointGui(*m_pRoutePointEditTarget)
9228 .GetDragHandlePoint(this);
9229 pSelect->AddSelectablePoint(
9230 dragHandlePoint.m_y, dragHandlePoint.m_x,
9231 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9232 }
9233 } else { // Deselect everything
9234 if (m_lastRoutePointEditTarget) {
9235 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9236 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9237 RoutePointGui(*m_lastRoutePointEditTarget)
9238 .EnableDragHandle(false);
9239 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9240 SELTYPE_DRAGHANDLE);
9241
9242 // Clear any routes being edited, probably orphans
9243 wxArrayPtrVoid *lastEditRouteArray =
9244 g_pRouteMan->GetRouteArrayContaining(
9245 m_lastRoutePointEditTarget);
9246 if (lastEditRouteArray) {
9247 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9248 ir++) {
9249 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9250 if (g_pRouteMan->IsRouteValid(pr)) {
9251 pr->m_bIsBeingEdited = false;
9252 }
9253 }
9254 delete lastEditRouteArray;
9255 }
9256 }
9257 }
9258
9259 // Do the refresh
9260
9261 if (g_bopengl) {
9262 InvalidateGL();
9263 Refresh(false);
9264 } else {
9265 if (m_lastRoutePointEditTarget) {
9266 wxRect wp_rect;
9267 RoutePointGui(*m_lastRoutePointEditTarget)
9268 .CalculateDCRect(m_dc_route, this, &wp_rect);
9269 RefreshRect(wp_rect, true);
9270 }
9271
9272 if (m_pRoutePointEditTarget) {
9273 wxRect wp_rect;
9274 RoutePointGui(*m_pRoutePointEditTarget)
9275 .CalculateDCRect(m_dc_route, this, &wp_rect);
9276 RefreshRect(wp_rect, true);
9277 }
9278 }
9279 }
9280 } // bSelectAllowed
9281
9282 // Check to see if there is a route or AIS target under the cursor
9283 // If so, start the rollover timer which creates the popup
9284 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9285 bool b_start_rollover = false;
9286 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9287 SelectItem *pFind = pSelectAIS->FindSelection(
9288 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9289 if (pFind) b_start_rollover = true;
9290 }
9291
9292 if (!b_start_rollover && !b_startedit_route) {
9293 SelectableItemList SelList = pSelect->FindSelectionList(
9294 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9295 wxSelectableItemListNode *node = SelList.GetFirst();
9296 while (node) {
9297 SelectItem *pFindSel = node->GetData();
9298
9299 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9300
9301 if (pr && pr->IsVisible()) {
9302 b_start_rollover = true;
9303 break;
9304 }
9305 node = node->GetNext();
9306 } // while
9307 }
9308
9309 if (!b_start_rollover && !b_startedit_route) {
9310 SelectableItemList SelList = pSelect->FindSelectionList(
9311 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9312 wxSelectableItemListNode *node = SelList.GetFirst();
9313 while (node) {
9314 SelectItem *pFindSel = node->GetData();
9315
9316 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9317
9318 if (tr && tr->IsVisible()) {
9319 b_start_rollover = true;
9320 break;
9321 }
9322 node = node->GetNext();
9323 } // while
9324 }
9325
9326 if (b_start_rollover)
9327 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9328 wxTIMER_ONE_SHOT);
9329 Route *tail = 0;
9330 Route *current = 0;
9331 bool appending = false;
9332 bool inserting = false;
9333 int connect = 0;
9334 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9335 // drag
9336 if (m_pRoutePointEditTarget) {
9337 // Check to see if there is a nearby point which may replace the
9338 // dragged one
9339 RoutePoint *pMousePoint = NULL;
9340
9341 int index_last;
9342 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9343 double nearby_radius_meters =
9344 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9345 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9346 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9347 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9348 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9349 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9350 bool duplicate =
9351 false; // ensure we won't create duplicate point in routes
9352 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9353 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9354 ir++) {
9355 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9356 if (pr && pr->pRoutePointList) {
9357 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9358 wxNOT_FOUND) {
9359 duplicate = true;
9360 break;
9361 }
9362 }
9363 }
9364 }
9365
9366 // Special case:
9367 // Allow "re-use" of a route's waypoints iff it is a simple
9368 // isolated route. This allows, for instance, creation of a closed
9369 // polygon route
9370 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9371
9372 if (!duplicate) {
9373 int dlg_return;
9374 dlg_return =
9375 OCPNMessageBox(this,
9376 _("Replace this RoutePoint by the nearby "
9377 "Waypoint?"),
9378 _("OpenCPN RoutePoint change"),
9379 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9380 if (dlg_return == wxID_YES) {
9381 /*double confirmation if the dragged point has been manually
9382 * created which can be important and could be deleted
9383 * unintentionally*/
9384
9385 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9386 pNearbyPoint);
9387 current =
9388 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9389
9390 if (tail && current && (tail != current)) {
9391 int dlg_return1;
9392 connect = tail->GetIndexOf(pNearbyPoint);
9393 int index_current_route =
9394 current->GetIndexOf(m_pRoutePointEditTarget);
9395 index_last = current->GetIndexOf(current->GetLastPoint());
9396 dlg_return1 = wxID_NO;
9397 if (index_last ==
9398 index_current_route) { // we are dragging the last
9399 // point of the route
9400 if (connect != tail->GetnPoints()) { // anything to do?
9401
9402 wxString dmsg(
9403 _("Last part of route to be appended to dragged "
9404 "route?"));
9405 if (connect == 1)
9406 dmsg =
9407 _("Full route to be appended to dragged route?");
9408
9409 dlg_return1 = OCPNMessageBox(
9410 this, dmsg, _("OpenCPN Route Create"),
9411 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9412 if (dlg_return1 == wxID_YES) {
9413 appending = true;
9414 }
9415 }
9416 } else if (index_current_route ==
9417 1) { // dragging the first point of the route
9418 if (connect != 1) { // anything to do?
9419
9420 wxString dmsg(
9421 _("First part of route to be inserted into dragged "
9422 "route?"));
9423 if (connect == tail->GetnPoints())
9424 dmsg = _(
9425 "Full route to be inserted into dragged route?");
9426
9427 dlg_return1 = OCPNMessageBox(
9428 this, dmsg, _("OpenCPN Route Create"),
9429 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9430 if (dlg_return1 == wxID_YES) {
9431 inserting = true;
9432 }
9433 }
9434 }
9435 }
9436
9437 if (m_pRoutePointEditTarget->IsShared()) {
9438 // dlg_return = wxID_NO;
9439 dlg_return = OCPNMessageBox(
9440 this,
9441 _("Do you really want to delete and replace this "
9442 "WayPoint") +
9443 _T("\n") + _("which has been created manually?"),
9444 ("OpenCPN RoutePoint warning"),
9445 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9446 }
9447 }
9448 if (dlg_return == wxID_YES) {
9449 pMousePoint = pNearbyPoint;
9450 if (pMousePoint->m_bIsolatedMark) {
9451 pMousePoint->SetShared(true);
9452 }
9453 pMousePoint->m_bIsolatedMark =
9454 false; // definitely no longer isolated
9455 pMousePoint->m_bIsInRoute = true;
9456 }
9457 }
9458 }
9459 }
9460 if (!pMousePoint)
9461 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9462
9463 if (m_pEditRouteArray) {
9464 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9465 ir++) {
9466 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9467 if (g_pRouteMan->IsRouteValid(pr)) {
9468 if (pMousePoint) { // remove the dragged point and insert the
9469 // nearby
9470 int nRP =
9471 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9472
9473 pSelect->DeleteAllSelectableRoutePoints(pr);
9474 pSelect->DeleteAllSelectableRouteSegments(pr);
9475
9476 pr->pRoutePointList->Insert(nRP, pMousePoint);
9477 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9478
9479 pSelect->AddAllSelectableRouteSegments(pr);
9480 pSelect->AddAllSelectableRoutePoints(pr);
9481 }
9482 pr->FinalizeForRendering();
9483 pr->UpdateSegmentDistances();
9484 if (m_bRoutePoinDragging) pConfig->UpdateRoute(pr);
9485 }
9486 }
9487 }
9488
9489 // Update the RouteProperties Dialog, if currently shown
9490 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9491 if (m_pEditRouteArray) {
9492 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9493 ir++) {
9494 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9495 if (g_pRouteMan->IsRouteValid(pr)) {
9496 if (pRoutePropDialog->GetRoute() == pr) {
9497 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9498 }
9499 /* cannot edit track points anyway
9500 else if ( ( NULL !=
9501 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9502 pTrackPropDialog->m_pTrack == pr ) {
9503 pTrackPropDialog->SetTrackAndUpdate(
9504 pr );
9505 }
9506 */
9507 }
9508 }
9509 }
9510 }
9511 if (pMousePoint) { // clear all about the dragged point
9512 pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9513 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9514 // Hide mark properties dialog if open on the replaced point
9515 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9516 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9517 g_pMarkInfoDialog->Hide();
9518
9519 delete m_pRoutePointEditTarget;
9520 m_lastRoutePointEditTarget = NULL;
9521 m_pRoutePointEditTarget = NULL;
9522 undo->AfterUndoableAction(pMousePoint);
9523 undo->InvalidateUndo();
9524 }
9525 }
9526 }
9527
9528 else if (m_bMarkEditing) { // End of way point drag
9529 if (m_pRoutePointEditTarget)
9530 if (m_bRoutePoinDragging)
9531 pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9532 }
9533
9534 if (m_pRoutePointEditTarget)
9535 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9536
9537 if (!m_pRoutePointEditTarget) {
9538 delete m_pEditRouteArray;
9539 m_pEditRouteArray = NULL;
9540 m_bRouteEditing = false;
9541 }
9542 m_bRoutePoinDragging = false;
9543
9544 if (appending) { // Appending to the route of which the last point is
9545 // dragged onto another route
9546
9547 // copy tail from connect until length to end of current after dragging
9548
9549 int length = tail->GetnPoints();
9550 for (int i = connect + 1; i <= length; i++) {
9551 current->AddPointAndSegment(tail->GetPoint(i), false);
9552 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9553 m_routeState++;
9554 gFrame->RefreshAllCanvas();
9555 ret = true;
9556 }
9557 current->FinalizeForRendering();
9558 current->m_bIsBeingEdited = false;
9559 FinishRoute();
9560 g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9561 }
9562 if (inserting) {
9563 pSelect->DeleteAllSelectableRoutePoints(current);
9564 pSelect->DeleteAllSelectableRouteSegments(current);
9565 for (int i = 1; i < connect; i++) { // numbering in the tail route
9566 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9567 }
9568 pSelect->AddAllSelectableRouteSegments(current);
9569 pSelect->AddAllSelectableRoutePoints(current);
9570 current->FinalizeForRendering();
9571 current->m_bIsBeingEdited = false;
9572 g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9573 }
9574
9575 // Update the RouteProperties Dialog, if currently shown
9576 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9577 if (m_pEditRouteArray) {
9578 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9579 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9580 if (g_pRouteMan->IsRouteValid(pr)) {
9581 if (pRoutePropDialog->GetRoute() == pr) {
9582 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9583 }
9584 }
9585 }
9586 }
9587 }
9588
9589 } // g_btouch
9590
9591 else { // !g_btouch
9592 if (m_bRouteEditing) { // End of RoutePoint drag
9593 Route *tail = 0;
9594 Route *current = 0;
9595 bool appending = false;
9596 bool inserting = false;
9597 int connect = 0;
9598 int index_last;
9599 if (m_pRoutePointEditTarget) {
9600 m_pRoutePointEditTarget->m_bBlink = false;
9601 // Check to see if there is a nearby point which may replace the
9602 // dragged one
9603 RoutePoint *pMousePoint = NULL;
9604 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9605 double nearby_radius_meters =
9606 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9607 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9608 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9609 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9610 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9611 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9612 bool duplicate = false; // don't create duplicate point in routes
9613 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9614 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9615 ir++) {
9616 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9617 if (pr && pr->pRoutePointList) {
9618 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9619 wxNOT_FOUND) {
9620 duplicate = true;
9621 break;
9622 }
9623 }
9624 }
9625 }
9626
9627 // Special case:
9628 // Allow "re-use" of a route's waypoints iff it is a simple
9629 // isolated route. This allows, for instance, creation of a closed
9630 // polygon route
9631 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9632
9633 if (!duplicate) {
9634 int dlg_return;
9635 dlg_return =
9636 OCPNMessageBox(this,
9637 _("Replace this RoutePoint by the nearby "
9638 "Waypoint?"),
9639 _("OpenCPN RoutePoint change"),
9640 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9641 if (dlg_return == wxID_YES) {
9642 /*double confirmation if the dragged point has been manually
9643 * created which can be important and could be deleted
9644 * unintentionally*/
9645 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9646 pNearbyPoint);
9647 current =
9648 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9649
9650 if (tail && current && (tail != current)) {
9651 int dlg_return1;
9652 connect = tail->GetIndexOf(pNearbyPoint);
9653 int index_current_route =
9654 current->GetIndexOf(m_pRoutePointEditTarget);
9655 index_last = current->GetIndexOf(current->GetLastPoint());
9656 dlg_return1 = wxID_NO;
9657 if (index_last ==
9658 index_current_route) { // we are dragging the last
9659 // point of the route
9660 if (connect != tail->GetnPoints()) { // anything to do?
9661
9662 wxString dmsg(
9663 _("Last part of route to be appended to dragged "
9664 "route?"));
9665 if (connect == 1)
9666 dmsg =
9667 _("Full route to be appended to dragged route?");
9668
9669 dlg_return1 = OCPNMessageBox(
9670 this, dmsg, _("OpenCPN Route Create"),
9671 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9672 if (dlg_return1 == wxID_YES) {
9673 appending = true;
9674 }
9675 }
9676 } else if (index_current_route ==
9677 1) { // dragging the first point of the route
9678 if (connect != 1) { // anything to do?
9679
9680 wxString dmsg(
9681 _("First part of route to be inserted into dragged "
9682 "route?"));
9683 if (connect == tail->GetnPoints())
9684 dmsg = _(
9685 "Full route to be inserted into dragged route?");
9686
9687 dlg_return1 = OCPNMessageBox(
9688 this, dmsg, _("OpenCPN Route Create"),
9689 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9690 if (dlg_return1 == wxID_YES) {
9691 inserting = true;
9692 }
9693 }
9694 }
9695 }
9696
9697 if (m_pRoutePointEditTarget->IsShared()) {
9698 dlg_return = wxID_NO;
9699 dlg_return = OCPNMessageBox(
9700 this,
9701 _("Do you really want to delete and replace this "
9702 "WayPoint") +
9703 _T("\n") + _("which has been created manually?"),
9704 ("OpenCPN RoutePoint warning"),
9705 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9706 }
9707 }
9708 if (dlg_return == wxID_YES) {
9709 pMousePoint = pNearbyPoint;
9710 if (pMousePoint->m_bIsolatedMark) {
9711 pMousePoint->SetShared(true);
9712 }
9713 pMousePoint->m_bIsolatedMark =
9714 false; // definitely no longer isolated
9715 pMousePoint->m_bIsInRoute = true;
9716 }
9717 }
9718 }
9719 }
9720 if (!pMousePoint)
9721 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9722
9723 if (m_pEditRouteArray) {
9724 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9725 ir++) {
9726 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9727 if (g_pRouteMan->IsRouteValid(pr)) {
9728 if (pMousePoint) { // replace dragged point by nearby one
9729 int nRP =
9730 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9731
9732 pSelect->DeleteAllSelectableRoutePoints(pr);
9733 pSelect->DeleteAllSelectableRouteSegments(pr);
9734
9735 pr->pRoutePointList->Insert(nRP, pMousePoint);
9736 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9737
9738 pSelect->AddAllSelectableRouteSegments(pr);
9739 pSelect->AddAllSelectableRoutePoints(pr);
9740 }
9741 pr->FinalizeForRendering();
9742 pr->UpdateSegmentDistances();
9743 pr->m_bIsBeingEdited = false;
9744
9745 if (m_bRoutePoinDragging) pConfig->UpdateRoute(pr);
9746
9747 pr->SetHiLite(0);
9748 }
9749 }
9750 Refresh(false);
9751 }
9752
9753 if (appending) {
9754 // copy tail from connect until length to end of current after
9755 // dragging
9756
9757 int length = tail->GetnPoints();
9758 for (int i = connect + 1; i <= length; i++) {
9759 current->AddPointAndSegment(tail->GetPoint(i), false);
9760 if (current)
9761 current->m_lastMousePointIndex = current->GetnPoints();
9762 m_routeState++;
9763 gFrame->RefreshAllCanvas();
9764 ret = true;
9765 }
9766 current->FinalizeForRendering();
9767 current->m_bIsBeingEdited = false;
9768 FinishRoute();
9769 g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9770 }
9771 if (inserting) {
9772 pSelect->DeleteAllSelectableRoutePoints(current);
9773 pSelect->DeleteAllSelectableRouteSegments(current);
9774 for (int i = 1; i < connect; i++) { // numbering in the tail route
9775 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9776 }
9777 pSelect->AddAllSelectableRouteSegments(current);
9778 pSelect->AddAllSelectableRoutePoints(current);
9779 current->FinalizeForRendering();
9780 current->m_bIsBeingEdited = false;
9781 g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9782 }
9783
9784 // Update the RouteProperties Dialog, if currently shown
9785 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9786 if (m_pEditRouteArray) {
9787 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9788 ir++) {
9789 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9790 if (g_pRouteMan->IsRouteValid(pr)) {
9791 if (pRoutePropDialog->GetRoute() == pr) {
9792 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9793 }
9794 }
9795 }
9796 }
9797 }
9798
9799 if (pMousePoint) {
9800 pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9801 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9802 // Hide mark properties dialog if open on the replaced point
9803 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9804 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9805 g_pMarkInfoDialog->Hide();
9806
9807 delete m_pRoutePointEditTarget;
9808 m_lastRoutePointEditTarget = NULL;
9809 undo->AfterUndoableAction(pMousePoint);
9810 undo->InvalidateUndo();
9811 } else {
9812 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9813 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9814
9815 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9816 }
9817
9818 delete m_pEditRouteArray;
9819 m_pEditRouteArray = NULL;
9820 }
9821
9822 InvalidateGL();
9823 m_bRouteEditing = false;
9824 m_pRoutePointEditTarget = NULL;
9825
9826 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9827 ret = true;
9828 }
9829
9830 else if (m_bMarkEditing) { // end of Waypoint drag
9831 if (m_pRoutePointEditTarget) {
9832 if (m_bRoutePoinDragging)
9833 pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9834 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9835 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9836 if (!g_bopengl) {
9837 wxRect wp_rect;
9838 RoutePointGui(*m_pRoutePointEditTarget)
9839 .CalculateDCRect(m_dc_route, this, &wp_rect);
9840 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9841 RefreshRect(wp_rect, true);
9842 }
9843 }
9844 m_pRoutePointEditTarget = NULL;
9845 m_bMarkEditing = false;
9846 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9847 ret = true;
9848 }
9849
9850 else if (leftIsDown) { // left click for chart center
9851 leftIsDown = false;
9852 ret = false;
9853
9854 if (!g_btouch) {
9855 if (!m_bChartDragging && !m_bMeasure_Active) {
9856 } else {
9857 m_bChartDragging = false;
9858 }
9859 }
9860 }
9861 m_bRoutePoinDragging = false;
9862 } // !btouch
9863
9864 if (ret) return true;
9865 } // left up
9866
9867 if (event.RightDown()) {
9868 SetFocus(); // This is to let a plugin know which canvas is right-clicked
9869 last_drag.x = mx;
9870 last_drag.y = my;
9871
9872 if (g_btouch) {
9873 // if( m_pRoutePointEditTarget )
9874 // return false;
9875 }
9876
9877 ret = true;
9878 m_FinishRouteOnKillFocus = false;
9879 CallPopupMenu(mx, my);
9880 m_FinishRouteOnKillFocus = true;
9881 } // Right down
9882
9883 return ret;
9884}
9885
9886bool panleftIsDown;
9887
9888bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
9889 // Skip all mouse processing if shift is held.
9890 // This allows plugins to implement shift+drag behaviors.
9891 if (event.ShiftDown()) {
9892 return false;
9893 }
9894 int x, y;
9895 event.GetPosition(&x, &y);
9896
9897 x *= m_displayScale;
9898 y *= m_displayScale;
9899
9900 // Check for wheel rotation
9901 // ideally, should be just longer than the time between
9902 // processing accumulated mouse events from the event queue
9903 // as would happen during screen redraws.
9904 int wheel_dir = event.GetWheelRotation();
9905
9906 if (wheel_dir) {
9907 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
9908 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
9909
9910 double factor = g_mouse_zoom_sensitivity;
9911 if (wheel_dir < 0) factor = 1 / factor;
9912
9913 if (g_bsmoothpanzoom) {
9914 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
9915 if (wheel_dir == m_last_wheel_dir) {
9916 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
9917 // m_zoom_target /= factor;
9918 } else
9919 StopMovement();
9920 } else {
9921 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
9922 m_wheelstopwatch.Start(0);
9923 // m_zoom_target = VPoint.chart_scale / factor;
9924 }
9925 }
9926
9927 m_last_wheel_dir = wheel_dir;
9928
9929 ZoomCanvas(factor, true, false);
9930 }
9931
9932 if (event.LeftDown()) {
9933 // Skip the first left click if it will cause a canvas focus shift
9934 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
9935 // printf("focus shift\n");
9936 return false;
9937 }
9938
9939 last_drag.x = x, last_drag.y = y;
9940 panleftIsDown = true;
9941 }
9942
9943 if (event.LeftUp()) {
9944 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
9945 // seen here.
9946 panleftIsDown = false;
9947
9948 if (!g_btouch) {
9949 if (!m_bChartDragging && !m_bMeasure_Active) {
9950 switch (cursor_region) {
9951 case MID_RIGHT: {
9952 PanCanvas(100, 0);
9953 break;
9954 }
9955
9956 case MID_LEFT: {
9957 PanCanvas(-100, 0);
9958 break;
9959 }
9960
9961 case MID_TOP: {
9962 PanCanvas(0, 100);
9963 break;
9964 }
9965
9966 case MID_BOT: {
9967 PanCanvas(0, -100);
9968 break;
9969 }
9970
9971 case CENTER: {
9972 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
9973 break;
9974 }
9975 }
9976 } else {
9977 m_bChartDragging = false;
9978 }
9979 }
9980 }
9981 }
9982
9983 if (event.Dragging() && event.LeftIsDown()) {
9984 /*
9985 * fixed dragging.
9986 * On my Surface Pro 3 running Arch Linux there is no mouse down event
9987 * before the drag event. Hence, as there is no mouse down event, last_drag
9988 * is not reset before the drag. And that results in one single drag
9989 * session, meaning you cannot drag the map a few miles north, lift your
9990 * finger, and the go even further north. Instead, the map resets itself
9991 * always to the very first drag start (since there is not reset of
9992 * last_drag).
9993 *
9994 * Besides, should not left down and dragging be enough of a situation to
9995 * start a drag procedure?
9996 *
9997 * Anyways, guarded it to be active in touch situations only.
9998 */
9999
10000 if (g_btouch) {
10001 struct timespec now;
10002 clock_gettime(CLOCK_MONOTONIC, &now);
10003 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10004
10005 if (false == m_bChartDragging) {
10006 // Reset drag calculation members
10007 last_drag.x = x, last_drag.y = y;
10008 m_bChartDragging = true;
10009 m_chart_drag_total_time = 0;
10010 m_chart_drag_total_x = 0;
10011 m_chart_drag_total_y = 0;
10012 m_inertia_last_drag_x = x;
10013 m_inertia_last_drag_y = y;
10014 m_drag_vec_x.clear();
10015 m_drag_vec_y.clear();
10016 m_drag_vec_t.clear();
10017 m_last_drag_time = tnow;
10018 }
10019
10020 // Calculate and store drag dynamics.
10021 uint64_t delta_t = tnow - m_last_drag_time;
10022 double delta_tf = delta_t / 1e9;
10023
10024 m_chart_drag_total_time += delta_tf;
10025 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10026 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10027
10028 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10029 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10030 m_drag_vec_t.push_back(delta_tf);
10031
10032 m_inertia_last_drag_x = x;
10033 m_inertia_last_drag_y = y;
10034 m_last_drag_time = tnow;
10035
10036 if ((last_drag.x != x) || (last_drag.y != y)) {
10037 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10038 // dragging on route create.
10039 // github #2994
10040 m_bChartDragging = true;
10041 StartTimedMovement();
10042 m_pan_drag.x += last_drag.x - x;
10043 m_pan_drag.y += last_drag.y - y;
10044 last_drag.x = x, last_drag.y = y;
10045 }
10046 }
10047 } else {
10048 if ((last_drag.x != x) || (last_drag.y != y)) {
10049 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10050 // dragging on route create.
10051 // github #2994
10052 m_bChartDragging = true;
10053 StartTimedMovement();
10054 m_pan_drag.x += last_drag.x - x;
10055 m_pan_drag.y += last_drag.y - y;
10056 last_drag.x = x, last_drag.y = y;
10057 }
10058 }
10059 }
10060
10061 // Handle some special cases
10062 if (g_btouch) {
10063 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10064 // deactivate next LeftUp to ovoid creating an unexpected point
10065 m_DoubleClickTimer->Start();
10066 singleClickEventIsValid = false;
10067 }
10068 }
10069 }
10070
10071 return true;
10072}
10073
10074void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10075 if (MouseEventOverlayWindows(event)) return;
10076
10077 if (MouseEventSetup(event)) return; // handled, no further action required
10078
10079 if (!MouseEventProcessObjects(event)) MouseEventProcessCanvas(event);
10080}
10081
10082void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10083 // Switch to the appropriate cursor on mouse movement
10084
10085 wxCursor *ptarget_cursor = pCursorArrow;
10086 if (!pPlugIn_Cursor) {
10087 ptarget_cursor = pCursorArrow;
10088 if ((!m_routeState) &&
10089 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10090 if (cursor_region == MID_RIGHT) {
10091 ptarget_cursor = pCursorRight;
10092 } else if (cursor_region == MID_LEFT) {
10093 ptarget_cursor = pCursorLeft;
10094 } else if (cursor_region == MID_TOP) {
10095 ptarget_cursor = pCursorDown;
10096 } else if (cursor_region == MID_BOT) {
10097 ptarget_cursor = pCursorUp;
10098 } else {
10099 ptarget_cursor = pCursorArrow;
10100 }
10101 } else if (m_bMeasure_Active ||
10102 m_routeState) // If Measure tool use Pencil Cursor
10103 ptarget_cursor = pCursorPencil;
10104 } else {
10105 ptarget_cursor = pPlugIn_Cursor;
10106 }
10107
10108 SetCursor(*ptarget_cursor);
10109}
10110
10111void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10112 SetCursor(*pCursorArrow);
10113}
10114
10115void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10116 ChartPlugInWrapper *target_plugin_chart = NULL;
10117 s57chart *Chs57 = NULL;
10118 wxFileName file;
10119 wxArrayString files;
10120
10121 ChartBase *target_chart = GetChartAtCursor();
10122 if (target_chart) {
10123 file.Assign(target_chart->GetFullPath());
10124 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10125 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10126 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10127 else
10128 Chs57 = dynamic_cast<s57chart *>(target_chart);
10129 } else { // target_chart = null, might be mbtiles
10130 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10131 unsigned int im = stackIndexArray.size();
10132 int scale = 2147483647; // max 32b integer
10133 if (VPoint.b_quilt && im > 0) {
10134 for (unsigned int is = 0; is < im; is++) {
10135 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10136 CHART_TYPE_MBTILES) {
10137 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10138 double lat, lon;
10139 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10140 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10141 .GetBBox()
10142 .Contains(lat, lon)) {
10143 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10144 scale) {
10145 scale =
10146 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10147 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10148 }
10149 }
10150 }
10151 }
10152 }
10153 }
10154
10155 std::vector<Ais8_001_22 *> area_notices;
10156
10157 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10158 float vp_scale = GetVPScale();
10159
10160 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10161 auto target_data = target.second;
10162 if (!target_data->area_notices.empty()) {
10163 for (auto &ani : target_data->area_notices) {
10164 Ais8_001_22 &area_notice = ani.second;
10165
10166 BoundingBox bbox;
10167
10168 for (Ais8_001_22_SubAreaList::iterator sa =
10169 area_notice.sub_areas.begin();
10170 sa != area_notice.sub_areas.end(); ++sa) {
10171 switch (sa->shape) {
10172 case AIS8_001_22_SHAPE_CIRCLE: {
10173 wxPoint target_point;
10174 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10175 bbox.Expand(target_point);
10176 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10177 break;
10178 }
10179 case AIS8_001_22_SHAPE_RECT: {
10180 wxPoint target_point;
10181 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10182 bbox.Expand(target_point);
10183 if (sa->e_dim_m > sa->n_dim_m)
10184 bbox.EnLarge(sa->e_dim_m * vp_scale);
10185 else
10186 bbox.EnLarge(sa->n_dim_m * vp_scale);
10187 break;
10188 }
10189 case AIS8_001_22_SHAPE_POLYGON:
10190 case AIS8_001_22_SHAPE_POLYLINE: {
10191 for (int i = 0; i < 4; ++i) {
10192 double lat = sa->latitude;
10193 double lon = sa->longitude;
10194 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10195 &lat, &lon);
10196 wxPoint target_point;
10197 GetCanvasPointPix(lat, lon, &target_point);
10198 bbox.Expand(target_point);
10199 }
10200 break;
10201 }
10202 case AIS8_001_22_SHAPE_SECTOR: {
10203 double lat1 = sa->latitude;
10204 double lon1 = sa->longitude;
10205 double lat, lon;
10206 wxPoint target_point;
10207 GetCanvasPointPix(lat1, lon1, &target_point);
10208 bbox.Expand(target_point);
10209 for (int i = 0; i < 18; ++i) {
10210 ll_gc_ll(
10211 lat1, lon1,
10212 sa->left_bound_deg +
10213 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10214 sa->radius_m / 1852.0, &lat, &lon);
10215 GetCanvasPointPix(lat, lon, &target_point);
10216 bbox.Expand(target_point);
10217 }
10218 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10219 &lat, &lon);
10220 GetCanvasPointPix(lat, lon, &target_point);
10221 bbox.Expand(target_point);
10222 break;
10223 }
10224 }
10225 }
10226
10227 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10228 area_notices.push_back(&area_notice);
10229 }
10230 }
10231 }
10232 }
10233 }
10234
10235 if (target_chart || !area_notices.empty() || file.HasName()) {
10236 // Go get the array of all objects at the cursor lat/lon
10237 int sel_rad_pix = 5;
10238 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10239
10240 // Make sure we always get the lights from an object, even if we are
10241 // currently not displaying lights on the chart.
10242
10243 SetCursor(wxCURSOR_WAIT);
10244 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10245 if (!lightsVis) SetShowENCLights(true);
10246 ;
10247
10248 ListOfObjRazRules *rule_list = NULL;
10249 ListOfPI_S57Obj *pi_rule_list = NULL;
10250 if (Chs57)
10251 rule_list =
10252 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10253 else if (target_plugin_chart)
10254 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10255 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10256
10257 ListOfObjRazRules *overlay_rule_list = NULL;
10258 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10259 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10260
10261 if (CHs57_Overlay) {
10262 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10263 zlat, zlon, SelectRadius, &GetVP());
10264 }
10265
10266 if (!lightsVis) SetShowENCLights(false);
10267
10268 wxString objText;
10269 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10270 wxString face = dFont->GetFaceName();
10271
10272 if (NULL == g_pObjectQueryDialog) {
10273 g_pObjectQueryDialog =
10274 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10275 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10276 }
10277
10278 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10279 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10280
10281#ifdef __WXOSX__
10282 // Auto Adjustment for dark mode
10283 fg = g_pObjectQueryDialog->GetForegroundColour();
10284#endif
10285
10286 objText.Printf(
10287 _T("<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>"),
10288 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10289
10290#ifdef __WXOSX__
10291 int points = dFont->GetPointSize();
10292#else
10293 int points = dFont->GetPointSize() + 1;
10294#endif
10295
10296 int sizes[7];
10297 for (int i = -2; i < 5; i++) {
10298 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10299 }
10300 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10301
10302 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += _T("<i>");
10303
10304 if (overlay_rule_list && CHs57_Overlay) {
10305 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10306 objText << _T("<hr noshade>");
10307 }
10308
10309 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10310 an != area_notices.end(); ++an) {
10311 objText << _T( "<b>AIS Area Notice:</b> " );
10312 objText << ais8_001_22_notice_names[(*an)->notice_type];
10313 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10314 (*an)->sub_areas.begin();
10315 sa != (*an)->sub_areas.end(); ++sa)
10316 if (!sa->text.empty()) objText << sa->text;
10317 objText << _T( "<br>expires: " ) << (*an)->expiry_time.Format();
10318 objText << _T( "<hr noshade>" );
10319 }
10320
10321 if (Chs57)
10322 objText << Chs57->CreateObjDescriptions(rule_list);
10323 else if (target_plugin_chart)
10324 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10325 pi_rule_list);
10326
10327 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << _T("</i>");
10328
10329 // Add the additional info files
10330 wxString AddFiles, filenameOK;
10331 int filecount = 0;
10332 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10333 // plugin
10334
10335 AddFiles = wxString::Format(
10336 _T("<hr noshade><br><b>Additional info files attached to: </b> ")
10337 _T("<font ")
10338 _T("size=-2>%s</font><br><table border=0 cellspacing=0 ")
10339 _T("cellpadding=3>"),
10340 file.GetFullName());
10341 file.Normalize();
10342 file.Assign(file.GetPath(), wxT(""));
10343 wxDir dir(file.GetFullPath());
10344 wxString filename;
10345 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10346 while (cont) {
10347 file.Assign(dir.GetNameWithSep().append(filename));
10348 wxString FormatString =
10349 _T("<td valign=top><font size=-2><a ")
10350 _T("href=\"%s\">%s</a></font></td>");
10351 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10352 filenameOK = file.GetFullPath(); // remember last valid name
10353 // we are making a 3 columns table. New row only every third file
10354 if (3 * ((int)filecount / 3) == filecount)
10355 FormatString.Prepend(_T("<tr>")); // new row
10356 else
10357 FormatString.Prepend(
10358 _T("<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>")); // an empty
10359 // spacer column
10360
10361 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10362 file.GetFullName());
10363 filecount++;
10364 }
10365 cont = dir.GetNext(&filename);
10366 }
10367 objText << AddFiles << _T("</table>");
10368 }
10369 objText << _T("</font>");
10370 objText << _T("</body></html>");
10371
10372 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10373 g_pObjectQueryDialog->SetHTMLPage(objText);
10374 g_pObjectQueryDialog->Show();
10375 }
10376 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10377 // generate an event to avoid double code
10378 wxHtmlLinkInfo hli(filenameOK);
10379 wxHtmlLinkEvent hle(1, hli);
10380 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10381 }
10382
10383 if (rule_list) rule_list->Clear();
10384 delete rule_list;
10385
10386 if (overlay_rule_list) overlay_rule_list->Clear();
10387 delete overlay_rule_list;
10388
10389 if (pi_rule_list) pi_rule_list->Clear();
10390 delete pi_rule_list;
10391
10392 SetCursor(wxCURSOR_ARROW);
10393 }
10394}
10395
10396void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10397 bool bNew = false;
10398 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10399 // Dialog
10400 g_pMarkInfoDialog = new MarkInfoDlg(this);
10401 bNew = true;
10402 }
10403
10404 if (1 /*g_bresponsive*/) {
10405 wxSize canvas_size = GetSize();
10406
10407 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10408 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10409
10410 g_pMarkInfoDialog->Layout();
10411
10412 wxPoint canvas_pos = GetPosition();
10413 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10414
10415 bool newFit = false;
10416 if (canvas_size.x < fitted_size.x) {
10417 fitted_size.x = canvas_size.x - 40;
10418 if (canvas_size.y < fitted_size.y)
10419 fitted_size.y -= 40; // scrollbar added
10420 }
10421 if (canvas_size.y < fitted_size.y) {
10422 fitted_size.y = canvas_size.y - 40;
10423 if (canvas_size.x < fitted_size.x)
10424 fitted_size.x -= 40; // scrollbar added
10425 }
10426
10427 if (newFit) {
10428 g_pMarkInfoDialog->SetSize(fitted_size);
10429 g_pMarkInfoDialog->Centre();
10430 }
10431 }
10432
10433 markPoint->m_bRPIsBeingEdited = false;
10434
10435 wxString title_base = _("Mark Properties");
10436 if (markPoint->m_bIsInRoute) {
10437 title_base = _("Waypoint Properties");
10438 }
10439 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10440 g_pMarkInfoDialog->UpdateProperties();
10441 if (markPoint->m_bIsInLayer) {
10442 wxString caption(wxString::Format(_T("%s, %s: %s"), title_base, _("Layer"),
10443 GetLayerName(markPoint->m_LayerID)));
10444 g_pMarkInfoDialog->SetDialogTitle(caption);
10445 } else
10446 g_pMarkInfoDialog->SetDialogTitle(title_base);
10447
10448 g_pMarkInfoDialog->Show();
10449 g_pMarkInfoDialog->Raise();
10450 g_pMarkInfoDialog->InitialFocus();
10451 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10452}
10453
10454void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10455 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10456 pRoutePropDialog->SetRouteAndUpdate(selected);
10457 // pNew->UpdateProperties();
10458 pRoutePropDialog->Show();
10459 pRoutePropDialog->Raise();
10460 return;
10461 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10462 this); // There is one global instance of the RouteProp Dialog
10463
10464 if (g_bresponsive) {
10465 wxSize canvas_size = GetSize();
10466 wxPoint canvas_pos = GetPosition();
10467 wxSize fitted_size = pRoutePropDialog->GetSize();
10468 ;
10469
10470 if (canvas_size.x < fitted_size.x) {
10471 fitted_size.x = canvas_size.x;
10472 if (canvas_size.y < fitted_size.y)
10473 fitted_size.y -= 20; // scrollbar added
10474 }
10475 if (canvas_size.y < fitted_size.y) {
10476 fitted_size.y = canvas_size.y;
10477 if (canvas_size.x < fitted_size.x)
10478 fitted_size.x -= 20; // scrollbar added
10479 }
10480
10481 pRoutePropDialog->SetSize(fitted_size);
10482 pRoutePropDialog->Centre();
10483
10484 // int xp = (canvas_size.x - fitted_size.x)/2;
10485 // int yp = (canvas_size.y - fitted_size.y)/2;
10486
10487 wxPoint xxp = ClientToScreen(canvas_pos);
10488 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10489 }
10490
10491 pRoutePropDialog->SetRouteAndUpdate(selected);
10492
10493 pRoutePropDialog->Show();
10494
10495 Refresh(false);
10496}
10497
10498void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10499 pTrackPropDialog = TrackPropDlg::getInstance(
10500 this); // There is one global instance of the RouteProp Dialog
10501
10502 pTrackPropDialog->SetTrackAndUpdate(selected);
10503 pTrackPropDialog->UpdateProperties();
10504
10505 pTrackPropDialog->Show();
10506
10507 Refresh(false);
10508}
10509
10510void pupHandler_PasteWaypoint() {
10511 Kml kml;
10512
10513 int pasteBuffer = kml.ParsePasteBuffer();
10514 RoutePoint *pasted = kml.GetParsedRoutePoint();
10515 if (!pasted) return;
10516
10517 double nearby_radius_meters =
10518 g_Platform->GetSelectRadiusPix() /
10519 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10520
10521 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10522 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10523
10524 int answer = wxID_NO;
10525 if (nearPoint && !nearPoint->m_bIsInLayer) {
10526 wxString msg;
10527 msg << _(
10528 "There is an existing waypoint at the same location as the one you are "
10529 "pasting. Would you like to merge the pasted data with it?\n\n");
10530 msg << _("Answering 'No' will create a new waypoint at the same location.");
10531 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10532 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10533 }
10534
10535 if (answer == wxID_YES) {
10536 nearPoint->SetName(pasted->GetName());
10537 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10538 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10539 pRouteManagerDialog->UpdateWptListCtrl();
10540 }
10541
10542 if (answer == wxID_NO) {
10543 RoutePoint *newPoint = new RoutePoint(pasted);
10544 newPoint->m_bIsolatedMark = true;
10545 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10546 newPoint);
10547 pConfig->AddNewWayPoint(newPoint, -1);
10548 pWayPointMan->AddRoutePoint(newPoint);
10549 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10550 pRouteManagerDialog->UpdateWptListCtrl();
10551 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10552 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10553 }
10554
10555 gFrame->InvalidateAllGL();
10556 gFrame->RefreshAllCanvas(false);
10557}
10558
10559void pupHandler_PasteRoute() {
10560 Kml kml;
10561
10562 int pasteBuffer = kml.ParsePasteBuffer();
10563 Route *pasted = kml.GetParsedRoute();
10564 if (!pasted) return;
10565
10566 double nearby_radius_meters =
10567 g_Platform->GetSelectRadiusPix() /
10568 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10569
10570 RoutePoint *curPoint;
10571 RoutePoint *nearPoint;
10572 RoutePoint *prevPoint = NULL;
10573
10574 bool mergepoints = false;
10575 bool createNewRoute = true;
10576 int existingWaypointCounter = 0;
10577
10578 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10579 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10580 nearPoint = pWayPointMan->GetNearbyWaypoint(
10581 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10582 if (nearPoint) {
10583 mergepoints = true;
10584 existingWaypointCounter++;
10585 // Small hack here to avoid both extending RoutePoint and repeating all
10586 // the GetNearbyWaypoint calculations. Use existin data field in
10587 // RoutePoint as temporary storage.
10588 curPoint->m_bPtIsSelected = true;
10589 }
10590 }
10591
10592 int answer = wxID_NO;
10593 if (mergepoints) {
10594 wxString msg;
10595 msg << _(
10596 "There are existing waypoints at the same location as some of the ones "
10597 "you are pasting. Would you like to just merge the pasted data into "
10598 "them?\n\n");
10599 msg << _("Answering 'No' will create all new waypoints for this route.");
10600 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10601 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10602
10603 if (answer == wxID_CANCEL) {
10604 return;
10605 }
10606 }
10607
10608 // If all waypoints exist since before, and a route with the same name, we
10609 // don't create a new route.
10610 if (mergepoints && answer == wxID_YES &&
10611 existingWaypointCounter == pasted->GetnPoints()) {
10612 wxRouteListNode *route_node = pRouteList->GetFirst();
10613 while (route_node) {
10614 Route *proute = route_node->GetData();
10615
10616 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10617 createNewRoute = false;
10618 break;
10619 }
10620 route_node = route_node->GetNext();
10621 }
10622 }
10623
10624 Route *newRoute = 0;
10625 RoutePoint *newPoint = 0;
10626
10627 if (createNewRoute) {
10628 newRoute = new Route();
10629 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10630 }
10631
10632 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10633 curPoint = pasted->GetPoint(i);
10634 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10635 curPoint->m_bPtIsSelected = false;
10636 newPoint = pWayPointMan->GetNearbyWaypoint(
10637 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10638 newPoint->SetName(curPoint->GetName());
10639 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10640
10641 if (createNewRoute) newRoute->AddPoint(newPoint);
10642 } else {
10643 curPoint->m_bPtIsSelected = false;
10644
10645 newPoint = new RoutePoint(curPoint);
10646 newPoint->m_bIsolatedMark = false;
10647 newPoint->SetIconName(_T("circle"));
10648 newPoint->m_bIsVisible = true;
10649 newPoint->m_bShowName = false;
10650 newPoint->SetShared(false);
10651
10652 newRoute->AddPoint(newPoint);
10653 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10654 newPoint);
10655 pConfig->AddNewWayPoint(newPoint, -1);
10656 pWayPointMan->AddRoutePoint(newPoint);
10657 }
10658 if (i > 1 && createNewRoute)
10659 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10660 curPoint->m_lat, curPoint->m_lon,
10661 prevPoint, newPoint, newRoute);
10662 prevPoint = newPoint;
10663 }
10664
10665 if (createNewRoute) {
10666 pRouteList->Append(newRoute);
10667 pConfig->AddNewRoute(newRoute); // use auto next num
10668
10669 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10670 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10671 }
10672
10673 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10674 pRouteManagerDialog->UpdateRouteListCtrl();
10675 pRouteManagerDialog->UpdateWptListCtrl();
10676 }
10677 gFrame->InvalidateAllGL();
10678 gFrame->RefreshAllCanvas(false);
10679 }
10680 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10681 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10682}
10683
10684void pupHandler_PasteTrack() {
10685 Kml kml;
10686
10687 int pasteBuffer = kml.ParsePasteBuffer();
10688 Track *pasted = kml.GetParsedTrack();
10689 if (!pasted) return;
10690
10691 TrackPoint *curPoint;
10692
10693 Track *newTrack = new Track();
10694 TrackPoint *newPoint;
10695 TrackPoint *prevPoint = NULL;
10696
10697 newTrack->SetName(pasted->GetName());
10698
10699 for (int i = 0; i < pasted->GetnPoints(); i++) {
10700 curPoint = pasted->GetPoint(i);
10701
10702 newPoint = new TrackPoint(curPoint);
10703
10704 wxDateTime now = wxDateTime::Now();
10705 newPoint->SetCreateTime(curPoint->GetCreateTime());
10706
10707 newTrack->AddPoint(newPoint);
10708
10709 if (prevPoint)
10710 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10711 newPoint->m_lat, newPoint->m_lon,
10712 prevPoint, newPoint, newTrack);
10713
10714 prevPoint = newPoint;
10715 }
10716
10717 g_TrackList.push_back(newTrack);
10718 pConfig->AddNewTrack(newTrack);
10719
10720 gFrame->InvalidateAllGL();
10721 gFrame->RefreshAllCanvas(false);
10722}
10723
10724bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
10725 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
10726 m_pFoundRoutePoint, m_FoundAIS_MMSI,
10727 m_pIDXCandidate, m_nmea_log);
10728
10729 Connect(
10730 wxEVT_COMMAND_MENU_SELECTED,
10731 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10732
10733 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
10734
10735 Disconnect(
10736 wxEVT_COMMAND_MENU_SELECTED,
10737 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10738
10739 delete m_canvasMenu;
10740 m_canvasMenu = NULL;
10741
10742#ifdef __WXQT__
10743 // gFrame->SurfaceToolbar();
10744 // g_MainToolbar->Raise();
10745#endif
10746
10747 return true;
10748}
10749
10750void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
10751 // Pass menu events from the canvas to the menu handler
10752 // This is necessarily in ChartCanvas since that is the menu's parent.
10753 if (m_canvasMenu) {
10754 m_canvasMenu->PopupMenuHandler(event);
10755 }
10756 return;
10757}
10758
10759void ChartCanvas::StartRoute(void) {
10760 // Do not allow more than one canvas to create a route at one time.
10761 if (g_brouteCreating) return;
10762
10763 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
10764
10765 g_brouteCreating = true;
10766 m_routeState = 1;
10767 m_bDrawingRoute = false;
10768 SetCursor(*pCursorPencil);
10769 // SetCanvasToolbarItemState(ID_ROUTE, true);
10770 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
10771
10772 HideGlobalToolbar();
10773
10774#ifdef __ANDROID__
10775 androidSetRouteAnnunciator(true);
10776#endif
10777}
10778
10779void ChartCanvas::FinishRoute(void) {
10780 m_routeState = 0;
10781 m_prev_pMousePoint = NULL;
10782 m_bDrawingRoute = false;
10783
10784 // SetCanvasToolbarItemState(ID_ROUTE, false);
10785 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
10786#ifdef __ANDROID__
10787 androidSetRouteAnnunciator(false);
10788#endif
10789
10790 SetCursor(*pCursorArrow);
10791
10792 if (m_pMouseRoute) {
10793 if (m_bAppendingRoute)
10794 pConfig->UpdateRoute(m_pMouseRoute);
10795 else {
10796 if (m_pMouseRoute->GetnPoints() > 1) {
10797 pConfig->AddNewRoute(m_pMouseRoute);
10798 } else {
10799 g_pRouteMan->DeleteRoute(m_pMouseRoute,
10800 NavObjectChanges::getInstance());
10801 m_pMouseRoute = NULL;
10802 }
10803 }
10804 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
10805
10806 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
10807 (pRoutePropDialog->IsShown())) {
10808 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
10809 }
10810
10811 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
10812 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10813 pRouteManagerDialog->UpdateRouteListCtrl();
10814 }
10815 }
10816 m_bAppendingRoute = false;
10817 m_pMouseRoute = NULL;
10818
10819 m_pSelectedRoute = NULL;
10820
10821 undo->InvalidateUndo();
10822 gFrame->RefreshAllCanvas(true);
10823
10824 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
10825
10826 ShowGlobalToolbar();
10827
10828 g_brouteCreating = false;
10829}
10830
10831void ChartCanvas::HideGlobalToolbar() {
10832 if (m_canvasIndex == 0) {
10833 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
10834 }
10835}
10836
10837void ChartCanvas::ShowGlobalToolbar() {
10838 if (m_canvasIndex == 0) {
10839 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
10840 }
10841}
10842
10843void ChartCanvas::ShowAISTargetList(void) {
10844 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
10845 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
10846 }
10847
10848 g_pAISTargetList->UpdateAISTargetList();
10849}
10850
10851void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
10852 if (!m_bShowOutlines) return;
10853
10854 if (!ChartData) return;
10855
10856 int nEntry = ChartData->GetChartTableEntries();
10857
10858 for (int i = 0; i < nEntry; i++) {
10859 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
10860
10861 // Check to see if the candidate chart is in the currently active group
10862 bool b_group_draw = false;
10863 if (m_groupIndex > 0) {
10864 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
10865 int index = pt->GetGroupArray()[ig];
10866 if (m_groupIndex == index) {
10867 b_group_draw = true;
10868 break;
10869 }
10870 }
10871 } else
10872 b_group_draw = true;
10873
10874 if (b_group_draw) RenderChartOutline(dc, i, vp);
10875 }
10876
10877 // On CM93 Composite Charts, draw the outlines of the next smaller
10878 // scale cell
10879 cm93compchart *pcm93 = NULL;
10880 if (VPoint.b_quilt) {
10881 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
10882 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
10883 pcm93 = (cm93compchart *)pch;
10884 break;
10885 }
10886 } else if (m_singleChart &&
10887 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
10888 pcm93 = (cm93compchart *)m_singleChart;
10889
10890 if (pcm93) {
10891 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
10892 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
10893
10894 if (zoom_factor > 8.0) {
10895 wxPen mPen(GetGlobalColor(_T("UINFM")), 2, wxPENSTYLE_SHORT_DASH);
10896 dc.SetPen(mPen);
10897 } else {
10898 wxPen mPen(GetGlobalColor(_T("UINFM")), 1, wxPENSTYLE_SOLID);
10899 dc.SetPen(mPen);
10900 }
10901
10902 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
10903 }
10904}
10905
10906void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
10907#ifdef ocpnUSE_GL
10908 if (g_bopengl && m_glcc) {
10909 /* opengl version specially optimized */
10910 m_glcc->RenderChartOutline(dc, dbIndex, vp);
10911 return;
10912 }
10913#endif
10914
10915 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
10916 if (!ChartData->IsChartAvailable(dbIndex)) return;
10917 }
10918
10919 float plylat, plylon;
10920 float plylat1, plylon1;
10921
10922 int pixx, pixy, pixx1, pixy1;
10923
10924 LLBBox box;
10925 ChartData->GetDBBoundingBox(dbIndex, box);
10926
10927 // Don't draw an outline in the case where the chart covers the entire world
10928 // */
10929 if (box.GetLonRange() == 360) return;
10930
10931 double lon_bias = 0;
10932 // chart is outside of viewport lat/lon bounding box
10933 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
10934
10935 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10936
10937 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
10938 dc.SetPen(wxPen(GetGlobalColor(_T ( "YELO1" )), 1, wxPENSTYLE_SOLID));
10939
10940 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
10941 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFG" )), 1, wxPENSTYLE_SOLID));
10942
10943 else
10944 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFR" )), 1, wxPENSTYLE_SOLID));
10945
10946 // Are there any aux ply entries?
10947 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
10948 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
10949 {
10950 wxPoint r, r1;
10951
10952 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10953 plylon += lon_bias;
10954
10955 GetCanvasPointPix(plylat, plylon, &r);
10956 pixx = r.x;
10957 pixy = r.y;
10958
10959 for (int i = 0; i < nPly - 1; i++) {
10960 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
10961 plylon1 += lon_bias;
10962
10963 GetCanvasPointPix(plylat1, plylon1, &r1);
10964 pixx1 = r1.x;
10965 pixy1 = r1.y;
10966
10967 int pixxs1 = pixx1;
10968 int pixys1 = pixy1;
10969
10970 bool b_skip = false;
10971
10972 if (vp.chart_scale > 5e7) {
10973 // calculate projected distance between these two points in meters
10974 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
10975 pow((double)(pixy1 - pixy), 2)) /
10976 vp.view_scale_ppm;
10977
10978 if (dist > 0.0) {
10979 // calculate GC distance between these two points in meters
10980 double distgc =
10981 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
10982
10983 // If the distances are nonsense, it means that the scale is very
10984 // small and the segment wrapped the world So skip it....
10985 // TODO improve this to draw two segments
10986 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
10987 b_skip = true;
10988 } else
10989 b_skip = true;
10990 }
10991
10992 ClipResult res = cohen_sutherland_line_clip_i(
10993 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
10994 if (res != Invisible && !b_skip)
10995 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
10996
10997 plylat = plylat1;
10998 plylon = plylon1;
10999 pixx = pixxs1;
11000 pixy = pixys1;
11001 }
11002
11003 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11004 plylon1 += lon_bias;
11005
11006 GetCanvasPointPix(plylat1, plylon1, &r1);
11007 pixx1 = r1.x;
11008 pixy1 = r1.y;
11009
11010 ClipResult res = cohen_sutherland_line_clip_i(
11011 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11012 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11013 }
11014
11015 else // Use Aux PlyPoints
11016 {
11017 wxPoint r, r1;
11018
11019 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11020 for (int j = 0; j < nAuxPlyEntries; j++) {
11021 int nAuxPly =
11022 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11023 GetCanvasPointPix(plylat, plylon, &r);
11024 pixx = r.x;
11025 pixy = r.y;
11026
11027 for (int i = 0; i < nAuxPly - 1; i++) {
11028 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11029
11030 GetCanvasPointPix(plylat1, plylon1, &r1);
11031 pixx1 = r1.x;
11032 pixy1 = r1.y;
11033
11034 int pixxs1 = pixx1;
11035 int pixys1 = pixy1;
11036
11037 bool b_skip = false;
11038
11039 if (vp.chart_scale > 5e7) {
11040 // calculate projected distance between these two points in meters
11041 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11042 ((pixy1 - pixy) * (pixy1 - pixy))) /
11043 vp.view_scale_ppm;
11044 if (dist > 0.0) {
11045 // calculate GC distance between these two points in meters
11046 double distgc =
11047 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11048
11049 // If the distances are nonsense, it means that the scale is very
11050 // small and the segment wrapped the world So skip it....
11051 // TODO improve this to draw two segments
11052 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11053 b_skip = true;
11054 } else
11055 b_skip = true;
11056 }
11057
11058 ClipResult res = cohen_sutherland_line_clip_i(
11059 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11060 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11061
11062 plylat = plylat1;
11063 plylon = plylon1;
11064 pixx = pixxs1;
11065 pixy = pixys1;
11066 }
11067
11068 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11069 GetCanvasPointPix(plylat1, plylon1, &r1);
11070 pixx1 = r1.x;
11071 pixy1 = r1.y;
11072
11073 ClipResult res = cohen_sutherland_line_clip_i(
11074 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11075 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11076 }
11077 }
11078}
11079
11080static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
11081 const wxString &second) {
11082 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11083
11084 int pointsize = dFont->GetPointSize();
11085 pointsize /= OCPN_GetWinDIPScaleFactor();
11086
11087 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11088 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11089 false, dFont->GetFaceName());
11090
11091 dc.SetFont(*psRLI_font);
11092
11093 int w1, h1;
11094 int w2 = 0;
11095 int h2 = 0;
11096 int h, w;
11097
11098 int xp, yp;
11099 int hilite_offset = 3;
11100#ifdef __WXMAC__
11101 wxScreenDC sdc;
11102 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
11103 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
11104#else
11105 dc.GetTextExtent(first, &w1, &h1);
11106 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
11107#endif
11108
11109 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11110 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11111
11112 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
11113 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11114
11115 h = h1 + h2;
11116
11117 xp = ref_point.x - w;
11118 yp = ref_point.y;
11119 yp += hilite_offset;
11120
11121 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor(_T ( "YELO1" )), 172);
11122
11123 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" ))));
11124 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
11125
11126 dc.DrawText(first, xp, yp);
11127 if (second.Len()) dc.DrawText(second, xp, yp + h1);
11128}
11129
11130void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11131 if (!g_bAllowShipToActive) return;
11132
11133 Route *rt = g_pRouteMan->GetpActiveRoute();
11134 if (!rt) return;
11135
11136 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11137 wxPoint2DDouble pa, pb;
11138 GetDoubleCanvasPointPix(gLat, gLon, &pa);
11139 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11140
11141 // set pen
11142 int width =
11143 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11144 if (rt->m_width != wxPENSTYLE_INVALID)
11145 width = rt->m_width; // set route pen style if any
11146 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11147 g_shipToActiveStyle, 5)]; // get setting pen style
11148 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11149 wxColour color =
11150 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11151 : // set setting route pen color
11152 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11153 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11154
11155 dc.SetPen(*mypen);
11156 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11157
11158 if (!Use_Opengl)
11159 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11160 (int)pb.m_y, GetVP(), true);
11161
11162#ifdef ocpnUSE_GL
11163 else {
11164#ifdef USE_ANDROID_GLES2
11165 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11166#else
11167 if (style != wxPENSTYLE_SOLID) {
11168 if (glChartCanvas::dash_map.find(style) !=
11169 glChartCanvas::dash_map.end()) {
11170 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11171 dc.SetPen(*mypen);
11172 }
11173 }
11174 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11175#endif
11176
11177 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11178 (int)pb.m_x, (int)pb.m_y, GetVP());
11179 }
11180#endif
11181 }
11182}
11183
11184void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11185 Route *route = 0;
11186 if (m_routeState >= 2) route = m_pMouseRoute;
11187 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11188 route = m_pMeasureRoute;
11189
11190 if (!route) return;
11191
11192 // Validate route pointer
11193 if (!g_pRouteMan->IsRouteValid(route)) return;
11194
11195 double render_lat = m_cursor_lat;
11196 double render_lon = m_cursor_lon;
11197
11198 int np = route->GetnPoints();
11199 if (np) {
11200 if (g_btouch && (np > 1)) np--;
11201 RoutePoint rp = route->GetPoint(np);
11202 render_lat = rp.m_lat;
11203 render_lon = rp.m_lon;
11204 }
11205
11206 double rhumbBearing, rhumbDist;
11207 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11208 &rhumbBearing, &rhumbDist);
11209 double brg = rhumbBearing;
11210 double dist = rhumbDist;
11211
11212 // Skip GreatCircle rubberbanding on touch devices.
11213 if (!g_btouch) {
11214 double gcBearing, gcBearing2, gcDist;
11215 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11216 m_cursor_lat, &gcDist, &gcBearing,
11217 &gcBearing2);
11218 double gcDistm = gcDist / 1852.0;
11219
11220 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11221 rhumbBearing = 90.;
11222
11223 wxPoint destPoint, lastPoint;
11224
11225 route->m_NextLegGreatCircle = false;
11226 int milesDiff = rhumbDist - gcDistm;
11227 if (milesDiff > 1) {
11228 brg = gcBearing;
11229 dist = gcDistm;
11230 route->m_NextLegGreatCircle = true;
11231 }
11232
11233 // FIXME (MacOS, the first segment is rendered wrong)
11234 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11235 &lastPoint);
11236
11237 if (route->m_NextLegGreatCircle) {
11238 for (int i = 1; i <= milesDiff; i++) {
11239 double p = (double)i * (1.0 / (double)milesDiff);
11240 double pLat, pLon;
11241 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11242 &pLon, &pLat, &gcBearing2);
11243 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11244 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11245 false);
11246 lastPoint = destPoint;
11247 }
11248 } else {
11249 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11250 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11251 false);
11252 if (m_bMeasure_DistCircle) {
11253 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11254 powf((float)(r_rband.y - lastPoint.y), 2));
11255
11256 dc.SetPen(*g_pRouteMan->GetRoutePen());
11257 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11258 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11259 }
11260 }
11261 }
11262 }
11263
11264 wxString routeInfo;
11265 if (g_bShowTrue)
11266 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11267 0x00B0);
11268
11269 if (g_bShowMag) {
11270 double latAverage = (m_cursor_lat + render_lat) / 2;
11271 double lonAverage = (m_cursor_lon + render_lon) / 2;
11272 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11273
11274 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11275 (int)varBrg, 0x00B0);
11276 }
11277
11278 routeInfo << _T(" ") << FormatDistanceAdaptive(dist);
11279
11280 wxString s0;
11281 if (!route->m_bIsInLayer)
11282 s0.Append(_("Route") + _T(": "));
11283 else
11284 s0.Append(_("Layer Route: "));
11285
11286 double disp_length = route->m_route_length;
11287 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11288 s0 += FormatDistanceAdaptive(disp_length);
11289
11290 RouteLegInfo(dc, r_rband, routeInfo, s0);
11291
11292 m_brepaint_piano = true;
11293}
11294
11295void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11296 if (!m_bShowVisibleSectors) return;
11297
11298 if (g_bDeferredInitDone) {
11299 // need to re-evaluate sectors?
11300 double rhumbBearing, rhumbDist;
11301 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11302 &rhumbBearing, &rhumbDist);
11303
11304 if (rhumbDist > 0.05) // miles
11305 {
11306 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11307 m_sectorlegsVisible);
11308 m_sector_glat = gLat;
11309 m_sector_glon = gLon;
11310 }
11311 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11312 }
11313}
11314
11315void ChartCanvas::WarpPointerDeferred(int x, int y) {
11316 warp_x = x;
11317 warp_y = y;
11318 warp_flag = true;
11319}
11320
11321int s_msg;
11322
11323void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11324 if (!ps52plib) return;
11325
11326 if (VPoint.b_quilt) { // quilted
11327 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11328
11329 if (m_pQuilt->IsQuiltVector()) {
11330 if (ps52plib->GetStateHash() != m_s52StateHash) {
11331 UpdateS52State();
11332 m_s52StateHash = ps52plib->GetStateHash();
11333 }
11334 }
11335 } else {
11336 if (ps52plib->GetStateHash() != m_s52StateHash) {
11337 UpdateS52State();
11338 m_s52StateHash = ps52plib->GetStateHash();
11339 }
11340 }
11341
11342 // Plugin charts
11343 bool bSendPlibState = true;
11344 if (VPoint.b_quilt) { // quilted
11345 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11346 }
11347
11348 if (bSendPlibState) {
11349 wxJSONValue v;
11350 v[_T("OpenCPN Version Major")] = VERSION_MAJOR;
11351 v[_T("OpenCPN Version Minor")] = VERSION_MINOR;
11352 v[_T("OpenCPN Version Patch")] = VERSION_PATCH;
11353 v[_T("OpenCPN Version Date")] = VERSION_DATE;
11354 v[_T("OpenCPN Version Full")] = VERSION_FULL;
11355
11356 // S52PLIB state
11357 v[_T("OpenCPN S52PLIB ShowText")] = GetShowENCText();
11358 v[_T("OpenCPN S52PLIB ShowSoundings")] = GetShowENCDepth();
11359 v[_T("OpenCPN S52PLIB ShowLights")] = GetShowENCLights();
11360 v[_T("OpenCPN S52PLIB ShowAnchorConditions")] = m_encShowAnchor;
11361 v[_T("OpenCPN S52PLIB ShowQualityOfData")] = GetShowENCDataQual();
11362 v[_T("OpenCPN S52PLIB ShowATONLabel")] = GetShowENCBuoyLabels();
11363 v[_T("OpenCPN S52PLIB ShowLightDescription")] = GetShowENCLightDesc();
11364
11365 v[_T("OpenCPN S52PLIB DisplayCategory")] = GetENCDisplayCategory();
11366
11367 v[_T("OpenCPN S52PLIB SoundingsFactor")] = g_ENCSoundingScaleFactor;
11368 v[_T("OpenCPN S52PLIB TextFactor")] = g_ENCTextScaleFactor;
11369
11370 // Global S52 options
11371
11372 v[_T("OpenCPN S52PLIB MetaDisplay")] = ps52plib->m_bShowMeta;
11373 v[_T("OpenCPN S52PLIB DeclutterText")] = ps52plib->m_bDeClutterText;
11374 v[_T("OpenCPN S52PLIB ShowNationalText")] = ps52plib->m_bShowNationalTexts;
11375 v[_T("OpenCPN S52PLIB ShowImportantTextOnly")] =
11376 ps52plib->m_bShowS57ImportantTextOnly;
11377 v[_T("OpenCPN S52PLIB UseSCAMIN")] = ps52plib->m_bUseSCAMIN;
11378 v[_T("OpenCPN S52PLIB UseSUPER_SCAMIN")] = ps52plib->m_bUseSUPER_SCAMIN;
11379 v[_T("OpenCPN S52PLIB SymbolStyle")] = ps52plib->m_nSymbolStyle;
11380 v[_T("OpenCPN S52PLIB BoundaryStyle")] = ps52plib->m_nBoundaryStyle;
11381 v[_T("OpenCPN S52PLIB ColorShades")] =
11382 S52_getMarinerParam(S52_MAR_TWO_SHADES);
11383
11384 // Some global GUI parameters, for completeness
11385 v[_T("OpenCPN Zoom Mod Vector")] = g_chart_zoom_modifier_vector;
11386 v[_T("OpenCPN Zoom Mod Other")] = g_chart_zoom_modifier_raster;
11387 v[_T("OpenCPN Scale Factor Exp")] =
11388 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11389 v[_T("OpenCPN Display Width")] = (int)g_display_size_mm;
11390
11391 wxJSONWriter w;
11392 wxString out;
11393 w.Write(v, out);
11394
11395 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11396 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11397 g_lastS52PLIBPluginMessage = out;
11398 }
11399 }
11400}
11401int spaint;
11402int s_in_update;
11403void ChartCanvas::OnPaint(wxPaintEvent &event) {
11404 wxPaintDC dc(this);
11405
11406 // GetToolbar()->Show( m_bToolbarEnable );
11407
11408 // Paint updates may have been externally disabled (temporarily, to avoid
11409 // Yield() recursion performance loss) It is important that the wxPaintDC is
11410 // built, even if we elect to not process this paint message. Otherwise, the
11411 // paint message may not be removed from the message queue, esp on Windows.
11412 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11413
11414 if (!m_b_paint_enable) {
11415 return;
11416 }
11417
11418 // If necessary, reconfigure the S52 PLIB
11419 UpdateCanvasS52PLIBConfig();
11420
11421#ifdef ocpnUSE_GL
11422 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11423
11424 if (m_glcc && g_bopengl) {
11425 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11426 s_in_update++;
11427 m_glcc->Update();
11428 s_in_update--;
11429 }
11430
11431 return;
11432 }
11433#endif
11434
11435 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11436
11437 wxRegion ru = GetUpdateRegion();
11438
11439 int rx, ry, rwidth, rheight;
11440 ru.GetBox(rx, ry, rwidth, rheight);
11441 // printf("%d Onpaint update region box: %d %d %d %d\n", spaint++, rx, ry,
11442 // rwidth, rheight);
11443
11444#ifdef ocpnUSE_DIBSECTION
11445 ocpnMemDC temp_dc;
11446#else
11447 wxMemoryDC temp_dc;
11448#endif
11449
11450 long height = GetVP().pix_height;
11451
11452#ifdef __WXMAC__
11453 // On OS X we have to explicitly extend the region for the piano area
11454 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11455 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11456 height += m_Piano->GetHeight();
11457#endif // __WXMAC__
11458 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11459
11460 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11461 if (pthumbwin) {
11462 int thumbx, thumby, thumbsx, thumbsy;
11463 pthumbwin->GetPosition(&thumbx, &thumby);
11464 pthumbwin->GetSize(&thumbsx, &thumbsy);
11465 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11466
11467 if (pthumbwin->IsShown()) {
11468 rgn_chart.Subtract(rgn_thumbwin);
11469 ru.Subtract(rgn_thumbwin);
11470 }
11471 }
11472
11473 // subtract the chart bar if it isn't transparent, and determine if we need to
11474 // paint it
11475 wxRegion rgn_blit = ru;
11476 if (g_bShowChartBar) {
11477 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11478 GetClientSize().x, m_Piano->GetHeight());
11479
11480 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11481 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11482 if (style->chartStatusWindowTransparent)
11483 m_brepaint_piano = true;
11484 else
11485 ru.Subtract(chart_bar_rect);
11486 }
11487 }
11488
11489 if (m_Compass && m_Compass->IsShown()) {
11490 wxRect compassRect = m_Compass->GetRect();
11491 if (ru.Contains(compassRect) != wxOutRegion) {
11492 ru.Subtract(compassRect);
11493 }
11494 }
11495
11496 wxRect noteRect = m_notification_button->GetRect();
11497 if (ru.Contains(noteRect) != wxOutRegion) {
11498 ru.Subtract(noteRect);
11499 }
11500
11501 // Is this viewpoint the same as the previously painted one?
11502 bool b_newview = true;
11503
11504 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11505 (m_cache_vp.rotation == VPoint.rotation) &&
11506 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11507 m_cache_vp.IsValid()) {
11508 b_newview = false;
11509 }
11510
11511 // If the ViewPort is skewed or rotated, we may be able to use the cached
11512 // rotated bitmap.
11513 bool b_rcache_ok = false;
11514 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11515 b_rcache_ok = !b_newview;
11516
11517 // Make a special VP
11518 if (VPoint.b_MercatorProjectionOverride)
11519 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11520 ViewPort svp = VPoint;
11521
11522 svp.pix_width = svp.rv_rect.width;
11523 svp.pix_height = svp.rv_rect.height;
11524
11525 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11526 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11527 // VPoint.rv_rect.height);
11528
11529 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11530
11531 // If we are going to use the cached rotated image, there is no need to fetch
11532 // any chart data and this will do it...
11533 if (b_rcache_ok) chart_get_region.Clear();
11534
11535 // Blit pan acceleration
11536 if (VPoint.b_quilt) // quilted
11537 {
11538 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11539
11540 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11541
11542 bool busy = false;
11543 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11544 m_cache_vp.rotation != VPoint.rotation)) {
11545 AbstractPlatform::ShowBusySpinner();
11546 busy = true;
11547 }
11548
11549 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11550 (m_working_bm.GetHeight() != svp.pix_height))
11551 m_working_bm.Create(svp.pix_width, svp.pix_height,
11552 -1); // make sure the target is big enoug
11553
11554 if (fabs(VPoint.rotation) < 0.01) {
11555 bool b_save = true;
11556
11557 if (g_SencThreadManager) {
11558 if (g_SencThreadManager->GetJobCount()) {
11559 b_save = false;
11560 m_cache_vp.Invalidate();
11561 }
11562 }
11563
11564 // If the saved wxBitmap from last OnPaint is useable
11565 // calculate the blit parameters
11566
11567 // We can only do screen blit painting if subsequent ViewPorts differ by
11568 // whole pixels So, in small scale bFollow mode, force the full screen
11569 // render. This seems a hack....There may be better logic here.....
11570
11571 // if(m_bFollow)
11572 // b_save = false;
11573
11574 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11575 if (b_newview) {
11576 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11577 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11578
11579 int dy = c_new.y - c_old.y;
11580 int dx = c_new.x - c_old.x;
11581
11582 // printf("In OnPaint Trying Blit dx: %d
11583 // dy:%d\n\n", dx, dy);
11584
11585 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11586 if (dx || dy) {
11587 // Blit the reuseable portion of the cached wxBitmap to a working
11588 // bitmap
11589 temp_dc.SelectObject(m_working_bm);
11590
11591 wxMemoryDC cache_dc;
11592 cache_dc.SelectObject(m_cached_chart_bm);
11593
11594 if (dy > 0) {
11595 if (dx > 0) {
11596 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11597 VPoint.pix_height - dy, &cache_dc, dx, dy);
11598 } else {
11599 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11600 VPoint.pix_height - dy, &cache_dc, 0, dy);
11601 }
11602
11603 } else {
11604 if (dx > 0) {
11605 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11606 VPoint.pix_height + dy, &cache_dc, dx, 0);
11607 } else {
11608 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11609 VPoint.pix_height + dy, &cache_dc, 0, 0);
11610 }
11611 }
11612
11613 OCPNRegion update_region;
11614 if (dy) {
11615 if (dy > 0)
11616 update_region.Union(
11617 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11618 else
11619 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11620 }
11621
11622 if (dx) {
11623 if (dx > 0)
11624 update_region.Union(
11625 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11626 else
11627 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11628 }
11629
11630 // Render the new region
11631 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11632 update_region);
11633 cache_dc.SelectObject(wxNullBitmap);
11634 } else {
11635 // No sensible (dx, dy) change in the view, so use the cached
11636 // member bitmap
11637 temp_dc.SelectObject(m_cached_chart_bm);
11638 b_save = false;
11639 }
11640 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11641
11642 } else // not blitable
11643 {
11644 temp_dc.SelectObject(m_working_bm);
11645 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11646 chart_get_region);
11647 }
11648 } else {
11649 // No change in the view, so use the cached member bitmap2
11650 temp_dc.SelectObject(m_cached_chart_bm);
11651 b_save = false;
11652 }
11653 } else // cached bitmap is not yet valid
11654 {
11655 temp_dc.SelectObject(m_working_bm);
11656 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11657 chart_get_region);
11658 }
11659
11660 // Save the fully rendered quilt image as a wxBitmap member of this class
11661 if (b_save) {
11662 // if((m_cached_chart_bm.GetWidth() !=
11663 // svp.pix_width) ||
11664 // (m_cached_chart_bm.GetHeight() !=
11665 // svp.pix_height))
11666 // m_cached_chart_bm.Create(svp.pix_width,
11667 // svp.pix_height, -1); // target wxBitmap
11668 // is big enough
11669 wxMemoryDC scratch_dc_0;
11670 scratch_dc_0.SelectObject(m_cached_chart_bm);
11671 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11672
11673 scratch_dc_0.SelectObject(wxNullBitmap);
11674
11675 m_bm_cache_vp =
11676 VPoint; // save the ViewPort associated with the cached wxBitmap
11677 }
11678 }
11679
11680 else // quilted, rotated
11681 {
11682 temp_dc.SelectObject(m_working_bm);
11683 OCPNRegion chart_get_all_region(
11684 wxRect(0, 0, svp.pix_width, svp.pix_height));
11685 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11686 chart_get_all_region);
11687 }
11688
11689 AbstractPlatform::HideBusySpinner();
11690
11691 }
11692
11693 else // not quilted
11694 {
11695 if (!m_singleChart) {
11696 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
11697 dc.Clear();
11698 return;
11699 }
11700
11701 if (!chart_get_region.IsEmpty()) {
11702 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
11703 }
11704 }
11705
11706 if (temp_dc.IsOk()) {
11707 // Arrange to render the World Chart vector data behind the rendered
11708 // current chart so that uncovered canvas areas show at least the world
11709 // chart.
11710 OCPNRegion chartValidRegion;
11711 if (!VPoint.b_quilt) {
11712 // Make a region covering the current chart on the canvas
11713
11714 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
11715 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
11716 else {
11717 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
11718 // require that the viewport passed here have pix_width and pix_height
11719 // set to the actual display, not the virtual (rv_rect) sizes
11720 // (the vector calculations require the virtual sizes in svp)
11721
11722 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
11723 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
11724 }
11725 } else
11726 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
11727
11728 temp_dc.DestroyClippingRegion();
11729
11730 // Copy current chart region
11731 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
11732
11733 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
11734
11735 if (!backgroundRegion.IsEmpty()) {
11736 // Draw the Background Chart only in the areas NOT covered by the
11737 // current chart view
11738
11739 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
11740 clipping regions with more than 1 rectangle so... */
11741 wxColour water = pWorldBackgroundChart->water;
11742 if (water.IsOk()) {
11743 temp_dc.SetPen(*wxTRANSPARENT_PEN);
11744 temp_dc.SetBrush(wxBrush(water));
11745 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
11746 while (upd.HaveRects()) {
11747 wxRect rect = upd.GetRect();
11748 temp_dc.DrawRectangle(rect);
11749 upd.NextRect();
11750 }
11751 }
11752 // Associate with temp_dc
11753 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
11754 temp_dc.SetDeviceClippingRegion(*clip_region);
11755 delete clip_region;
11756
11757 ocpnDC bgdc(temp_dc);
11758 double r = VPoint.rotation;
11759 SetVPRotation(VPoint.skew);
11760
11761 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
11762 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
11763
11764 SetVPRotation(r);
11765 }
11766 } // temp_dc.IsOk();
11767
11768 wxMemoryDC *pChartDC = &temp_dc;
11769 wxMemoryDC rotd_dc;
11770
11771 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
11772 // Can we use the current rotated image cache?
11773 if (!b_rcache_ok) {
11774#ifdef __WXMSW__
11775 wxMemoryDC tbase_dc;
11776 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
11777 tbase_dc.SelectObject(bm_base);
11778 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11779 tbase_dc.SelectObject(wxNullBitmap);
11780#else
11781 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
11782#endif
11783
11784 wxImage base_image;
11785 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
11786
11787 // Use a local static image rotator to improve wxWidgets code profile
11788 // Especially, on GTK the wxRound and wxRealPoint functions are very
11789 // expensive.....
11790
11791 double angle = GetVP().skew - GetVP().rotation;
11792 wxImage ri;
11793 bool b_rot_ok = false;
11794 if (base_image.IsOk()) {
11795 ViewPort rot_vp = GetVP();
11796
11797 m_b_rot_hidef = false;
11798
11799 ri = Image_Rotate(
11800 base_image, angle,
11801 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
11802 m_b_rot_hidef, &m_roffset);
11803
11804 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11805 (rot_vp.rotation == VPoint.rotation) &&
11806 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
11807 rot_vp.IsValid() && (ri.IsOk())) {
11808 b_rot_ok = true;
11809 }
11810 }
11811
11812 if (b_rot_ok) {
11813 delete m_prot_bm;
11814 m_prot_bm = new wxBitmap(ri);
11815 }
11816
11817 m_roffset.x += VPoint.rv_rect.x;
11818 m_roffset.y += VPoint.rv_rect.y;
11819 }
11820
11821 if (m_prot_bm && m_prot_bm->IsOk()) {
11822 rotd_dc.SelectObject(*m_prot_bm);
11823 pChartDC = &rotd_dc;
11824 } else {
11825 pChartDC = &temp_dc;
11826 m_roffset = wxPoint(0, 0);
11827 }
11828 } else { // unrotated
11829 pChartDC = &temp_dc;
11830 m_roffset = wxPoint(0, 0);
11831 }
11832
11833 wxPoint offset = m_roffset;
11834
11835 // Save the PixelCache viewpoint for next time
11836 m_cache_vp = VPoint;
11837
11838 // Set up a scratch DC for overlay objects
11839 wxMemoryDC mscratch_dc;
11840 mscratch_dc.SelectObject(*pscratch_bm);
11841
11842 mscratch_dc.ResetBoundingBox();
11843 mscratch_dc.DestroyClippingRegion();
11844 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
11845
11846 // Blit the externally invalidated areas of the chart onto the scratch dc
11847 wxRegionIterator upd(rgn_blit); // get the update rect list
11848 while (upd) {
11849 wxRect rect = upd.GetRect();
11850
11851 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
11852 rect.x - offset.x, rect.y - offset.y);
11853 upd++;
11854 }
11855
11856 // If multi-canvas, indicate which canvas has keyboard focus
11857 // by drawing a simple blue bar at the top.
11858 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
11859 if (this == wxWindow::FindFocus()) {
11860 g_focusCanvas = this;
11861
11862 wxColour colour = GetGlobalColor(_T("BLUE4"));
11863 mscratch_dc.SetPen(wxPen(colour));
11864 mscratch_dc.SetBrush(wxBrush(colour));
11865
11866 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
11867 mscratch_dc.DrawRectangle(activeRect);
11868 }
11869 }
11870
11871 // Any MBtiles?
11872 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
11873 unsigned int im = stackIndexArray.size();
11874 if (VPoint.b_quilt && im > 0) {
11875 std::vector<int> tiles_to_show;
11876 for (unsigned int is = 0; is < im; is++) {
11877 const ChartTableEntry &cte =
11878 ChartData->GetChartTableEntry(stackIndexArray[is]);
11879 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
11880 continue;
11881 }
11882 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
11883 tiles_to_show.push_back(stackIndexArray[is]);
11884 }
11885 }
11886
11887 if (tiles_to_show.size())
11888 SetAlertString(_("MBTile requires OpenGL to be enabled"));
11889 }
11890
11891 // May get an unexpected OnPaint call while switching display modes
11892 // Guard for that.
11893 if (!g_bopengl) {
11894 // Draw the rest of the overlay objects directly on the scratch dc
11895 ocpnDC scratch_dc(mscratch_dc);
11896 DrawOverlayObjects(scratch_dc, ru);
11897
11898 if (m_bShowTide) {
11899 RebuildTideSelectList(GetVP().GetBBox());
11900 DrawAllTidesInBBox(scratch_dc, GetVP().GetBBox());
11901 }
11902
11903 if (m_bShowCurrent) {
11904 RebuildCurrentSelectList(GetVP().GetBBox());
11905 DrawAllCurrentsInBBox(scratch_dc, GetVP().GetBBox());
11906 }
11907
11908 if (m_brepaint_piano && g_bShowChartBar) {
11909 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), mscratch_dc);
11910 }
11911
11912 if (m_Compass) m_Compass->Paint(scratch_dc);
11913
11914 auto &noteman = NotificationManager::GetInstance();
11915 if (noteman.GetNotificationCount()) {
11916 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
11917 if (m_notification_button->UpdateStatus()) Refresh();
11918 m_notification_button->Show(true);
11919 m_notification_button->Paint(scratch_dc);
11920 } else {
11921 m_notification_button->Show(false);
11922 }
11923
11924 RenderAlertMessage(mscratch_dc, GetVP());
11925 }
11926
11927 // quiting?
11928 if (g_bquiting) {
11929#ifdef ocpnUSE_DIBSECTION
11930 ocpnMemDC q_dc;
11931#else
11932 wxMemoryDC q_dc;
11933#endif
11934 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
11935 q_dc.SelectObject(qbm);
11936
11937 // Get a copy of the screen
11938 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
11939
11940 // Draw a rectangle over the screen with a stipple brush
11941 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
11942 q_dc.SetBrush(qbr);
11943 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
11944
11945 // Blit back into source
11946 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
11947 wxCOPY);
11948
11949 q_dc.SelectObject(wxNullBitmap);
11950 }
11951#if 0
11952 // It is possible that this two-step method may be reuired for some platforms.
11953 // So, retain in the code base to aid recovery if necessary
11954
11955 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
11956 if( VPoint.b_quilt ) {
11957 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
11958 ChartBase *chart = m_pQuilt->GetRefChart();
11959 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
11960
11961 // Clear the text Global declutter list
11962 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
11963 if(ChPI)
11964 ChPI->ClearPLIBTextList();
11965 else{
11966 if(ps52plib)
11967 ps52plib->ClearTextList();
11968 }
11969
11970 wxMemoryDC t_dc;
11971 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
11972
11973 wxColor maskBackground = wxColour(1,0,0);
11974 t_dc.SelectObject( qbm );
11975 t_dc.SetBackground(wxBrush(maskBackground));
11976 t_dc.Clear();
11977
11978 // Copy the scratch DC into the new bitmap
11979 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
11980
11981 // Render the text to the new bitmap
11982 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
11983 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
11984
11985 // Copy the new bitmap back to the scratch dc
11986 wxRegionIterator upd_final( ru );
11987 while( upd_final ) {
11988 wxRect rect = upd_final.GetRect();
11989 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
11990 upd_final++;
11991 }
11992
11993 t_dc.SelectObject( wxNullBitmap );
11994 }
11995 }
11996 }
11997#endif
11998 // Direct rendering model...
11999 if (VPoint.b_quilt) {
12000 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12001 ChartBase *chart = m_pQuilt->GetRefChart();
12002 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12003 // Clear the text Global declutter list
12004 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12005 if (ChPI)
12006 ChPI->ClearPLIBTextList();
12007 else {
12008 if (ps52plib) ps52plib->ClearTextList();
12009 }
12010
12011 // Render the text directly to the scratch bitmap
12012 OCPNRegion chart_all_text_region(
12013 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12014
12015 if (g_bShowChartBar && m_Piano) {
12016 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12017 GetVP().pix_width, m_Piano->GetHeight());
12018
12019 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12020 if (!style->chartStatusWindowTransparent)
12021 chart_all_text_region.Subtract(chart_bar_rect);
12022 }
12023
12024 if (m_Compass && m_Compass->IsShown()) {
12025 wxRect compassRect = m_Compass->GetRect();
12026 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12027 chart_all_text_region.Subtract(compassRect);
12028 }
12029 }
12030
12031 mscratch_dc.DestroyClippingRegion();
12032
12033 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12034 chart_all_text_region);
12035 }
12036 }
12037 }
12038
12039 // And finally, blit the scratch dc onto the physical dc
12040 wxRegionIterator upd_final(rgn_blit);
12041 while (upd_final) {
12042 wxRect rect = upd_final.GetRect();
12043 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12044 rect.y);
12045 upd_final++;
12046 }
12047
12048 // Test code to validate the dc drawing rectangle....
12049 /*
12050 wxRegionIterator upd_ru ( rgn_blit ); // get the update rect list
12051 while ( upd_ru )
12052 {
12053 wxRect rect = upd_ru.GetRect();
12054
12055 dc.SetPen(wxPen(*wxRED));
12056 dc.SetBrush(wxBrush(*wxRED, wxTRANSPARENT));
12057 dc.DrawRectangle(rect);
12058 upd_ru ++ ;
12059 }
12060 */
12061
12062 // Deselect the chart bitmap from the temp_dc, so that it will not be
12063 // destroyed in the temp_dc dtor
12064 temp_dc.SelectObject(wxNullBitmap);
12065 // And for the scratch bitmap
12066 mscratch_dc.SelectObject(wxNullBitmap);
12067
12068 dc.DestroyClippingRegion();
12069
12070 PaintCleanup();
12071}
12072
12073void ChartCanvas::PaintCleanup() {
12074 // Handle the current graphic window, if present
12075
12076 if (pCwin) {
12077 pCwin->Show();
12078 if (m_bTCupdate) {
12079 pCwin->Refresh();
12080 pCwin->Update();
12081 }
12082 }
12083
12084 // And set flags for next time
12085 m_bTCupdate = false;
12086
12087 // Handle deferred WarpPointer
12088 if (warp_flag) {
12089 WarpPointer(warp_x, warp_y);
12090 warp_flag = false;
12091 }
12092
12093 // Start movement timers, this runs nearly immediately.
12094 // the reason we cannot simply call it directly is the
12095 // refresh events it emits may be blocked from this paint event
12096 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12097 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12098}
12099
12100#if 0
12101wxColour GetErrorGraphicColor(double val)
12102{
12103 /*
12104 double valm = wxMin(val_max, val);
12105
12106 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12107 unsigned char red = (unsigned char)(255 * (valm/val_max));
12108
12109 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12110
12111 hv.saturation = 1.0;
12112 hv.value = 1.0;
12113
12114 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12115 return wxColour(rv.red, rv.green, rv.blue);
12116 */
12117
12118 // HTML colors taken from NOAA WW3 Web representation
12119 wxColour c;
12120 if((val > 0) && (val < 1)) c.Set(_T("#002ad9"));
12121 else if((val >= 1) && (val < 2)) c.Set(_T("#006ed9"));
12122 else if((val >= 2) && (val < 3)) c.Set(_T("#00b2d9"));
12123 else if((val >= 3) && (val < 4)) c.Set(_T("#00d4d4"));
12124 else if((val >= 4) && (val < 5)) c.Set(_T("#00d9a6"));
12125 else if((val >= 5) && (val < 7)) c.Set(_T("#00d900"));
12126 else if((val >= 7) && (val < 9)) c.Set(_T("#95d900"));
12127 else if((val >= 9) && (val < 12)) c.Set(_T("#d9d900"));
12128 else if((val >= 12) && (val < 15)) c.Set(_T("#d9ae00"));
12129 else if((val >= 15) && (val < 18)) c.Set(_T("#d98300"));
12130 else if((val >= 18) && (val < 21)) c.Set(_T("#d95700"));
12131 else if((val >= 21) && (val < 24)) c.Set(_T("#d90000"));
12132 else if((val >= 24) && (val < 27)) c.Set(_T("#ae0000"));
12133 else if((val >= 27) && (val < 30)) c.Set(_T("#8c0000"));
12134 else if((val >= 30) && (val < 36)) c.Set(_T("#870000"));
12135 else if((val >= 36) && (val < 42)) c.Set(_T("#690000"));
12136 else if((val >= 42) && (val < 48)) c.Set(_T("#550000"));
12137 else if( val >= 48) c.Set(_T("#410000"));
12138
12139 return c;
12140}
12141
12142void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12143{
12144 wxImage gr_image(vp->pix_width, vp->pix_height);
12145 gr_image.InitAlpha();
12146
12147 double maxval = -10000;
12148 double minval = 10000;
12149
12150 double rlat, rlon;
12151 double glat, glon;
12152
12153 GetCanvasPixPoint(0, 0, rlat, rlon);
12154
12155 for(int i=1; i < vp->pix_height-1; i++)
12156 {
12157 for(int j=0; j < vp->pix_width; j++)
12158 {
12159 // Reference mercator value
12160// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12161
12162 // Georef value
12163 GetCanvasPixPoint(j, i, glat, glon);
12164
12165 maxval = wxMax(maxval, (glat - rlat));
12166 minval = wxMin(minval, (glat - rlat));
12167
12168 }
12169 rlat = glat;
12170 }
12171
12172 GetCanvasPixPoint(0, 0, rlat, rlon);
12173 for(int i=1; i < vp->pix_height-1; i++)
12174 {
12175 for(int j=0; j < vp->pix_width; j++)
12176 {
12177 // Reference mercator value
12178// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12179
12180 // Georef value
12181 GetCanvasPixPoint(j, i, glat, glon);
12182
12183 double f = ((glat - rlat)-minval)/(maxval - minval);
12184
12185 double dy = (f * 40);
12186
12187 wxColour c = GetErrorGraphicColor(dy);
12188 unsigned char r = c.Red();
12189 unsigned char g = c.Green();
12190 unsigned char b = c.Blue();
12191
12192 gr_image.SetRGB(j, i, r,g,b);
12193 if((glat - rlat )!= 0)
12194 gr_image.SetAlpha(j, i, 128);
12195 else
12196 gr_image.SetAlpha(j, i, 255);
12197
12198 }
12199 rlat = glat;
12200 }
12201
12202 // Create a Bitmap
12203 wxBitmap *pbm = new wxBitmap(gr_image);
12204 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12205 pbm->SetMask(gr_mask);
12206
12207 pmdc->DrawBitmap(*pbm, 0,0);
12208
12209 delete pbm;
12210
12211}
12212
12213#endif
12214
12215void ChartCanvas::CancelMouseRoute() {
12216 m_routeState = 0;
12217 m_pMouseRoute = NULL;
12218 m_bDrawingRoute = false;
12219}
12220
12221int ChartCanvas::GetNextContextMenuId() {
12222 return CanvasMenuHandler::GetNextContextMenuId();
12223}
12224
12225bool ChartCanvas::SetCursor(const wxCursor &c) {
12226#ifdef ocpnUSE_GL
12227 if (g_bopengl && m_glcc)
12228 return m_glcc->SetCursor(c);
12229 else
12230#endif
12231 return wxWindow::SetCursor(c);
12232}
12233
12234void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12235 if (g_bquiting) return;
12236 // Keep the mouse position members up to date
12237 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12238
12239 // Retrigger the route leg popup timer
12240 // This handles the case when the chart is moving in auto-follow mode,
12241 // but no user mouse input is made. The timer handler may Hide() the
12242 // popup if the chart moved enough n.b. We use slightly longer oneshot
12243 // value to allow this method's Refresh() to complete before potentially
12244 // getting another Refresh() in the popup timer handler.
12245 if (!m_RolloverPopupTimer.IsRunning() &&
12246 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12247 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12248 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12249 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12250
12251#ifdef ocpnUSE_GL
12252 if (m_glcc && g_bopengl) {
12253 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12254 // overlay objects.
12255 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12256
12257 m_glcc->Refresh(eraseBackground,
12258 NULL); // We always are going to render the entire screen
12259 // anyway, so make
12260 // sure that the window managers understand the invalid area
12261 // is actually the entire client area.
12262
12263 // We need to selectively Refresh some child windows, if they are visible.
12264 // Note that some children are refreshed elsewhere on timer ticks, so don't
12265 // need attention here.
12266
12267 // Thumbnail chart
12268 if (pthumbwin && pthumbwin->IsShown()) {
12269 pthumbwin->Raise();
12270 pthumbwin->Refresh(false);
12271 }
12272
12273 // ChartInfo window
12274 if (m_pCIWin && m_pCIWin->IsShown()) {
12275 m_pCIWin->Raise();
12276 m_pCIWin->Refresh(false);
12277 }
12278
12279 // if(g_MainToolbar)
12280 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12281
12282 } else
12283#endif
12284 wxWindow::Refresh(eraseBackground, rect);
12285}
12286
12287void ChartCanvas::Update() {
12288 if (m_glcc && g_bopengl) {
12289#ifdef ocpnUSE_GL
12290 m_glcc->Update();
12291#endif
12292 } else
12293 wxWindow::Update();
12294}
12295
12296void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12297 if (!pemboss) return;
12298 int x = pemboss->x, y = pemboss->y;
12299 const double factor = 200;
12300
12301 wxASSERT_MSG(dc.GetDC(), wxT("DrawEmboss has no dc (opengl?)"));
12302 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12303 wxASSERT_MSG(pmdc, wxT("dc to EmbossCanvas not a memory dc"));
12304
12305 // Grab a snipped image out of the chart
12306 wxMemoryDC snip_dc;
12307 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12308 snip_dc.SelectObject(snip_bmp);
12309
12310 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12311 snip_dc.SelectObject(wxNullBitmap);
12312
12313 wxImage snip_img = snip_bmp.ConvertToImage();
12314
12315 // Apply Emboss map to the snip image
12316 unsigned char *pdata = snip_img.GetData();
12317 if (pdata) {
12318 for (int y = 0; y < pemboss->height; y++) {
12319 int map_index = (y * pemboss->width);
12320 for (int x = 0; x < pemboss->width; x++) {
12321 double val = (pemboss->pmap[map_index] * factor) / 256.;
12322
12323 int nred = (int)((*pdata) + val);
12324 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12325 *pdata++ = (unsigned char)nred;
12326
12327 int ngreen = (int)((*pdata) + val);
12328 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12329 *pdata++ = (unsigned char)ngreen;
12330
12331 int nblue = (int)((*pdata) + val);
12332 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12333 *pdata++ = (unsigned char)nblue;
12334
12335 map_index++;
12336 }
12337 }
12338 }
12339
12340 // Convert embossed snip to a bitmap
12341 wxBitmap emb_bmp(snip_img);
12342
12343 // Map to another memoryDC
12344 wxMemoryDC result_dc;
12345 result_dc.SelectObject(emb_bmp);
12346
12347 // Blit to target
12348 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12349
12350 result_dc.SelectObject(wxNullBitmap);
12351}
12352
12353emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12354 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12355
12356 if (GetQuiltMode()) {
12357 // disable Overzoom indicator for MBTiles
12358 int refIndex = GetQuiltRefChartdbIndex();
12359 if (refIndex >= 0) {
12360 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12361 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12362 if (current_type == CHART_TYPE_MBTILES) {
12363 ChartBase *pChart = m_pQuilt->GetRefChart();
12364 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12365 if (ptc) {
12366 zoom_factor = ptc->GetZoomFactor();
12367 }
12368 }
12369 }
12370
12371 if (zoom_factor <= 3.9) return NULL;
12372 } else {
12373 if (m_singleChart) {
12374 if (zoom_factor <= 3.9) return NULL;
12375 } else
12376 return NULL;
12377 }
12378
12379 if (m_pEM_OverZoom) {
12380 m_pEM_OverZoom->x = 4;
12381 m_pEM_OverZoom->y = 0;
12382 if (g_MainToolbar && IsPrimaryCanvas()) {
12383 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12384 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12385 }
12386 }
12387 return m_pEM_OverZoom;
12388}
12389
12390void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12391 GridDraw(dc);
12392
12393 // bool pluginOverlayRender = true;
12394 //
12395 // if(g_canvasConfig > 0){ // Multi canvas
12396 // if(IsPrimaryCanvas())
12397 // pluginOverlayRender = false;
12398 // }
12399
12400 g_overlayCanvas = this;
12401
12402 if (g_pi_manager) {
12403 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12404 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12405 OVERLAY_LEGACY);
12406 }
12407
12408 AISDrawAreaNotices(dc, GetVP(), this);
12409
12410 wxDC *pdc = dc.GetDC();
12411 if (pdc) {
12412 pdc->DestroyClippingRegion();
12413 wxDCClipper(*pdc, ru);
12414 }
12415
12416 if (m_bShowNavobjects) {
12417 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12418 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12419 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12420 DrawAnchorWatchPoints(dc);
12421 } else {
12422 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12423 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12424 }
12425
12426 AISDraw(dc, GetVP(), this);
12427 ShipDraw(dc);
12428 AlertDraw(dc);
12429
12430 RenderVisibleSectorLights(dc);
12431
12432 RenderAllChartOutlines(dc, GetVP());
12433 RenderRouteLegs(dc);
12434 RenderShipToActive(dc, false);
12435 ScaleBarDraw(dc);
12436 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12437 if (g_pi_manager) {
12438 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12439 OVERLAY_OVER_SHIPS);
12440 }
12441
12442 DrawEmboss(dc, EmbossDepthScale());
12443 DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12444 if (g_pi_manager) {
12445 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12446 OVERLAY_OVER_EMBOSS);
12447 }
12448 if (!g_PrintingInProgress) {
12449 if (IsPrimaryCanvas()) {
12450 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12451 }
12452
12453 if (IsPrimaryCanvas()) {
12454 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12455 }
12456
12457 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12458
12459 if (m_pTrackRolloverWin) {
12460 m_pTrackRolloverWin->Draw(dc);
12461 m_brepaint_piano = true;
12462 }
12463
12464 if (m_pRouteRolloverWin) {
12465 m_pRouteRolloverWin->Draw(dc);
12466 m_brepaint_piano = true;
12467 }
12468
12469 if (m_pAISRolloverWin) {
12470 m_pAISRolloverWin->Draw(dc);
12471 m_brepaint_piano = true;
12472 }
12473 }
12474 if (g_pi_manager) {
12475 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12476 OVERLAY_OVER_UI);
12477 }
12478}
12479
12480emboss_data *ChartCanvas::EmbossDepthScale() {
12481 if (!m_bShowDepthUnits) return NULL;
12482
12483 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12484
12485 if (GetQuiltMode()) {
12486 wxString s = m_pQuilt->GetQuiltDepthUnit();
12487 s.MakeUpper();
12488 if (s == _T("FEET"))
12489 depth_unit_type = DEPTH_UNIT_FEET;
12490 else if (s.StartsWith(_T("FATHOMS")))
12491 depth_unit_type = DEPTH_UNIT_FATHOMS;
12492 else if (s.StartsWith(_T("METERS")))
12493 depth_unit_type = DEPTH_UNIT_METERS;
12494 else if (s.StartsWith(_T("METRES")))
12495 depth_unit_type = DEPTH_UNIT_METERS;
12496 else if (s.StartsWith(_T("METRIC")))
12497 depth_unit_type = DEPTH_UNIT_METERS;
12498 else if (s.StartsWith(_T("METER")))
12499 depth_unit_type = DEPTH_UNIT_METERS;
12500
12501 } else {
12502 if (m_singleChart) {
12503 depth_unit_type = m_singleChart->GetDepthUnitType();
12504 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12505 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12506 }
12507 }
12508
12509 emboss_data *ped = NULL;
12510 switch (depth_unit_type) {
12511 case DEPTH_UNIT_FEET:
12512 ped = m_pEM_Feet;
12513 break;
12514 case DEPTH_UNIT_METERS:
12515 ped = m_pEM_Meters;
12516 break;
12517 case DEPTH_UNIT_FATHOMS:
12518 ped = m_pEM_Fathoms;
12519 break;
12520 default:
12521 return NULL;
12522 }
12523
12524 ped->x = (GetVP().pix_width - ped->width);
12525
12526 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12527 wxRect r = m_Compass->GetRect();
12528 ped->y = r.y + r.height;
12529 } else {
12530 ped->y = 40;
12531 }
12532 return ped;
12533}
12534
12535void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12536 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12537 wxFont font;
12538 if (style->embossFont == wxEmptyString) {
12539 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12540 font = *dFont;
12541 font.SetPointSize(60);
12542 font.SetWeight(wxFONTWEIGHT_BOLD);
12543 } else
12544 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12545 wxFONTWEIGHT_BOLD, false, style->embossFont);
12546
12547 int emboss_width = 500;
12548 int emboss_height = 200;
12549
12550 // Free any existing emboss maps
12551 delete m_pEM_Feet;
12552 delete m_pEM_Meters;
12553 delete m_pEM_Fathoms;
12554
12555 // Create the 3 DepthUnit emboss map structures
12556 m_pEM_Feet =
12557 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12558 m_pEM_Meters =
12559 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12560 m_pEM_Fathoms =
12561 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12562}
12563
12564#define OVERZOOM_TEXT _("OverZoom")
12565
12566void ChartCanvas::SetOverzoomFont() {
12567 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12568 int w, h;
12569
12570 wxFont font;
12571 if (style->embossFont == wxEmptyString) {
12572 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12573 font = *dFont;
12574 font.SetPointSize(40);
12575 font.SetWeight(wxFONTWEIGHT_BOLD);
12576 } else
12577 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12578 wxFONTWEIGHT_BOLD, false, style->embossFont);
12579
12580 wxClientDC dc(this);
12581 dc.SetFont(font);
12582 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12583
12584 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12585 font.SetPointSize(font.GetPointSize() - 1);
12586 dc.SetFont(font);
12587 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12588 }
12589 m_overzoomFont = font;
12590 m_overzoomTextWidth = w;
12591 m_overzoomTextHeight = h;
12592}
12593
12594void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12595 delete m_pEM_OverZoom;
12596
12597 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12598 m_pEM_OverZoom =
12599 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12600 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12601}
12602
12603emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12604 int height, const wxString &str,
12605 ColorScheme cs) {
12606 int *pmap;
12607
12608 // Create a temporary bitmap
12609 wxBitmap bmp(width, height, -1);
12610
12611 // Create a memory DC
12612 wxMemoryDC temp_dc;
12613 temp_dc.SelectObject(bmp);
12614
12615 // Paint on it
12616 temp_dc.SetBackground(*wxWHITE_BRUSH);
12617 temp_dc.SetTextBackground(*wxWHITE);
12618 temp_dc.SetTextForeground(*wxBLACK);
12619
12620 temp_dc.Clear();
12621
12622 temp_dc.SetFont(font);
12623
12624 int str_w, str_h;
12625 temp_dc.GetTextExtent(str, &str_w, &str_h);
12626 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12627 temp_dc.DrawText(str, 1, 1);
12628
12629 // Deselect the bitmap
12630 temp_dc.SelectObject(wxNullBitmap);
12631
12632 // Convert bitmap the wxImage for manipulation
12633 wxImage img = bmp.ConvertToImage();
12634
12635 int image_width = str_w * 105 / 100;
12636 int image_height = str_h * 105 / 100;
12637 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12638 wxMin(image_height, img.GetHeight()));
12639 wxImage imgs = img.GetSubImage(r);
12640
12641 double val_factor;
12642 switch (cs) {
12643 case GLOBAL_COLOR_SCHEME_DAY:
12644 default:
12645 val_factor = 1;
12646 break;
12647 case GLOBAL_COLOR_SCHEME_DUSK:
12648 val_factor = .5;
12649 break;
12650 case GLOBAL_COLOR_SCHEME_NIGHT:
12651 val_factor = .25;
12652 break;
12653 }
12654
12655 int val;
12656 int index;
12657 const int w = imgs.GetWidth();
12658 const int h = imgs.GetHeight();
12659 pmap = (int *)calloc(w * h * sizeof(int), 1);
12660 // Create emboss map by differentiating the emboss image
12661 // and storing integer results in pmap
12662 // n.b. since the image is B/W, it is sufficient to check
12663 // one channel (i.e. red) only
12664 for (int y = 1; y < h - 1; y++) {
12665 for (int x = 1; x < w - 1; x++) {
12666 val =
12667 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
12668 val = (int)(val * val_factor);
12669 index = (y * w) + x;
12670 pmap[index] = val;
12671 }
12672 }
12673
12674 emboss_data *pret = new emboss_data;
12675 pret->pmap = pmap;
12676 pret->width = w;
12677 pret->height = h;
12678
12679 return pret;
12680}
12681
12682void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12683 Track *active_track = NULL;
12684 for (Track *pTrackDraw : g_TrackList) {
12685 if (g_pActiveTrack == pTrackDraw) {
12686 active_track = pTrackDraw;
12687 continue;
12688 }
12689
12690 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
12691 }
12692
12693 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12694}
12695
12696void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12697 Track *active_track = NULL;
12698 for (Track *pTrackDraw : g_TrackList) {
12699 if (g_pActiveTrack == pTrackDraw) {
12700 active_track = pTrackDraw;
12701 break;
12702 }
12703 }
12704 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12705}
12706
12707void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12708 Route *active_route = NULL;
12709
12710 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12711 node = node->GetNext()) {
12712 Route *pRouteDraw = node->GetData();
12713 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12714 active_route = pRouteDraw;
12715 continue;
12716 }
12717
12718 // if(m_canvasIndex == 1)
12719 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
12720 }
12721
12722 // Draw any active or selected route (or track) last, so that is is always on
12723 // top
12724 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12725}
12726
12727void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12728 Route *active_route = NULL;
12729
12730 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12731 node = node->GetNext()) {
12732 Route *pRouteDraw = node->GetData();
12733 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12734 active_route = pRouteDraw;
12735 break;
12736 }
12737 }
12738 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12739}
12740
12741void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12742 if (!pWayPointMan) return;
12743
12744 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12745
12746 while (node) {
12747 RoutePoint *pWP = node->GetData();
12748 if (pWP) {
12749 if (pWP->m_bIsInRoute) {
12750 node = node->GetNext();
12751 continue;
12752 }
12753
12754 /* technically incorrect... waypoint has bounding box */
12755 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
12756 RoutePointGui(*pWP).Draw(dc, this, NULL);
12757 else {
12758 // Are Range Rings enabled?
12759 if (pWP->GetShowWaypointRangeRings() &&
12760 (pWP->GetWaypointRangeRingsNumber() > 0)) {
12761 double factor = 1.00;
12762 if (pWP->GetWaypointRangeRingsStepUnits() ==
12763 1) // convert kilometers to NMi
12764 factor = 1 / 1.852;
12765
12766 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
12767 pWP->GetWaypointRangeRingsStep() / 60.;
12768 radius *= 2; // Fudge factor
12769
12770 LLBBox radar_box;
12771 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
12772 pWP->m_lat + radius, pWP->m_lon + radius);
12773 if (!BltBBox.IntersectOut(radar_box)) {
12774 RoutePointGui(*pWP).Draw(dc, this, NULL);
12775 }
12776 }
12777 }
12778 }
12779
12780 node = node->GetNext();
12781 }
12782}
12783
12784void ChartCanvas::DrawBlinkObjects(void) {
12785 // All RoutePoints
12786 wxRect update_rect;
12787
12788 if (!pWayPointMan) return;
12789
12790 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12791
12792 while (node) {
12793 RoutePoint *pWP = node->GetData();
12794 if (pWP) {
12795 if (pWP->m_bBlink) {
12796 update_rect.Union(pWP->CurrentRect_in_DC);
12797 }
12798 }
12799
12800 node = node->GetNext();
12801 }
12802 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
12803}
12804
12805void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
12806 // draw anchor watch rings, if activated
12807
12808 if (pAnchorWatchPoint1 || pAnchorWatchPoint2) {
12809 wxPoint r1, r2;
12810 wxPoint lAnchorPoint1, lAnchorPoint2;
12811 double lpp1 = 0.0;
12812 double lpp2 = 0.0;
12813 if (pAnchorWatchPoint1) {
12814 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
12815 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
12816 &lAnchorPoint1);
12817 }
12818 if (pAnchorWatchPoint2) {
12819 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
12820 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
12821 &lAnchorPoint2);
12822 }
12823
12824 wxPen ppPeng(GetGlobalColor(_T ( "UGREN" )), 2);
12825 wxPen ppPenr(GetGlobalColor(_T ( "URED" )), 2);
12826
12827 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
12828 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
12829 dc.SetBrush(*ppBrush);
12830
12831 if (lpp1 > 0) {
12832 dc.SetPen(ppPeng);
12833 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12834 }
12835
12836 if (lpp2 > 0) {
12837 dc.SetPen(ppPeng);
12838 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12839 }
12840
12841 if (lpp1 < 0) {
12842 dc.SetPen(ppPenr);
12843 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12844 }
12845
12846 if (lpp2 < 0) {
12847 dc.SetPen(ppPenr);
12848 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12849 }
12850 }
12851}
12852
12853double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
12854 double lpp = 0.;
12855 wxPoint r1;
12856 wxPoint lAnchorPoint;
12857 double d1 = 0.0;
12858 double dabs;
12859 double tlat1, tlon1;
12860
12861 if (pAnchorWatchPoint) {
12862 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
12863 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
12864 dabs = fabs(d1 / 1852.);
12865 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
12866 &tlat1, &tlon1);
12867 GetCanvasPointPix(tlat1, tlon1, &r1);
12868 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
12869 &lAnchorPoint);
12870 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
12871 pow((double)(lAnchorPoint.y - r1.y), 2));
12872
12873 // This is an entry watch
12874 if (d1 < 0) lpp = -lpp;
12875 }
12876 return lpp;
12877}
12878
12879//------------------------------------------------------------------------------------------
12880// Tides Support
12881//------------------------------------------------------------------------------------------
12882void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
12883 if (!ptcmgr) return;
12884
12885 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
12886
12887 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12888 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12889 double lon = pIDX->IDX_lon;
12890 double lat = pIDX->IDX_lat;
12891
12892 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12893 if ((type == 't') || (type == 'T')) {
12894 if (BBox.Contains(lat, lon)) {
12895 // Manage the point selection list
12896 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
12897 }
12898 }
12899 }
12900}
12901
12902extern wxDateTime gTimeSource;
12903
12904void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
12905 if (!ptcmgr) return;
12906
12907 wxDateTime this_now = gTimeSource;
12908 bool cur_time = !gTimeSource.IsValid();
12909 if (cur_time) this_now = wxDateTime::Now();
12910 time_t t_this_now = this_now.GetTicks();
12911
12912 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
12913 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
12914 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
12915 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )), 1,
12916 wxPENSTYLE_SOLID);
12917 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
12918 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )), 1,
12919 wxPENSTYLE_SOLID);
12920
12921 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
12922 GetGlobalColor(_T ( "GREEN1" )), wxBRUSHSTYLE_SOLID);
12923 // wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush (
12924 // GetGlobalColor ( _T ( "UINFD" ) ), wxSOLID );
12925 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
12926 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )),
12927 wxBRUSHSTYLE_SOLID);
12928 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
12929 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )),
12930 wxBRUSHSTYLE_SOLID);
12931
12932 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
12933 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
12934 int font_size = wxMax(10, dFont->GetPointSize());
12935 font_size /= g_Platform->GetDisplayDIPMult(this);
12936 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
12937 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
12938 false, dFont->GetFaceName());
12939
12940 dc.SetPen(*pblack_pen);
12941 dc.SetBrush(*pgreen_brush);
12942
12943 wxBitmap bm;
12944 switch (m_cs) {
12945 case GLOBAL_COLOR_SCHEME_DAY:
12946 bm = m_bmTideDay;
12947 break;
12948 case GLOBAL_COLOR_SCHEME_DUSK:
12949 bm = m_bmTideDusk;
12950 break;
12951 case GLOBAL_COLOR_SCHEME_NIGHT:
12952 bm = m_bmTideNight;
12953 break;
12954 default:
12955 bm = m_bmTideDay;
12956 break;
12957 }
12958
12959 int bmw = bm.GetWidth();
12960 int bmh = bm.GetHeight();
12961
12962 float scale_factor = 1.0;
12963
12964 // Set the onscreen size of the symbol
12965 // Compensate for various display resolutions
12966 float icon_pixelRefDim = 45;
12967
12968#if 0
12969 float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *25 / 1000; // Intended physical rendered size onscreen
12970 nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 8);
12971 nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 15);
12972 float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
12973#endif
12974
12975#ifndef __ANDROID__
12976 // another method is simply to declare that the icon shall be x times the size
12977 // of a raster symbol (e.g.BOYLAT)
12978 // This is a bit of a hack that will suffice until until we get fully
12979 // scalable ENC symbol sets
12980 // float nominal_icon_size_pixels = 48; // 3 x 16
12981 // float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
12982
12983 // or, x times size of text font
12984 wxScreenDC sdc;
12985 int height;
12986 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
12987 height *= g_Platform->GetDisplayDIPMult(this);
12988 float nominal_icon_size_pixels = 48; // 3 x 16
12989 float pix_factor = (2 * height) / nominal_icon_size_pixels;
12990
12991#else
12992 // Yet another method goes like this:
12993 // Set the onscreen size of the symbol
12994 // Compensate for various display resolutions
12995 // Develop empirically, making a symbol about 16 mm tall
12996 double symHeight =
12997 icon_pixelRefDim /
12998 GetPixPerMM(); // from draw instructions, symbol is xx pix high
12999 double targetHeight0 = 16.0;
13000
13001 // But we want to scale the size down for smaller displays
13002 double displaySize = m_display_size_mm;
13003 displaySize = wxMax(displaySize, 100);
13004
13005 float targetHeight = wxMin(targetHeight0, displaySize / 15);
13006
13007 double pix_factor = targetHeight / symHeight;
13008#endif
13009
13010 scale_factor *= pix_factor;
13011
13012 float user_scale_factor = g_ChartScaleFactorExp;
13013 if (g_ChartScaleFactorExp > 1.0)
13014 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13015 1.2; // soften the scale factor a bit
13016
13017 scale_factor *= user_scale_factor;
13018 scale_factor *= GetContentScaleFactor();
13019
13020 {
13021 double marge = 0.05;
13022 std::vector<LLBBox> drawn_boxes;
13023 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13024 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13025
13026 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13027 if ((type == 't') || (type == 'T')) // only Tides
13028 {
13029 double lon = pIDX->IDX_lon;
13030 double lat = pIDX->IDX_lat;
13031
13032 if (BBox.ContainsMarge(lat, lon, marge)) {
13033 // Avoid drawing detailed graphic for duplicate tide stations
13034 if (GetVP().chart_scale < 500000) {
13035 bool bdrawn = false;
13036 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13037 if (drawn_boxes[i].Contains(lat, lon)) {
13038 bdrawn = true;
13039 break;
13040 }
13041 }
13042 if (bdrawn) continue; // the station loop
13043
13044 LLBBox this_box;
13045 this_box.Set(lat, lon, lat, lon);
13046 this_box.EnLarge(.005);
13047 drawn_boxes.push_back(this_box);
13048 }
13049
13050 wxPoint r;
13051 GetCanvasPointPix(lat, lon, &r);
13052 // draw standard icons
13053 if (GetVP().chart_scale > 500000) {
13054 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13055 }
13056 // draw "extended" icons
13057 else {
13058 dc.SetFont(*plabelFont);
13059 {
13060 {
13061 float val, nowlev;
13062 float ltleve = 0.;
13063 float htleve = 0.;
13064 time_t tctime;
13065 time_t lttime = 0;
13066 time_t httime = 0;
13067 bool wt;
13068 // define if flood or ebb in the last ten minutes and verify if
13069 // data are useable
13070 if (ptcmgr->GetTideFlowSens(
13071 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13072 pIDX->IDX_rec_num, nowlev, val, wt)) {
13073 // search forward the first HW or LW near "now" ( starting at
13074 // "now" - ten minutes )
13075 ptcmgr->GetHightOrLowTide(
13076 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13077 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13078 wt, pIDX->IDX_rec_num, val, tctime);
13079 if (wt) {
13080 httime = tctime;
13081 htleve = val;
13082 } else {
13083 lttime = tctime;
13084 ltleve = val;
13085 }
13086 wt = !wt;
13087
13088 // then search opposite tide near "now"
13089 if (tctime > t_this_now) // search backward
13090 ptcmgr->GetHightOrLowTide(
13091 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13092 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13093 pIDX->IDX_rec_num, val, tctime);
13094 else
13095 // or search forward
13096 ptcmgr->GetHightOrLowTide(
13097 t_this_now, FORWARD_TEN_MINUTES_STEP,
13098 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13099 val, tctime);
13100 if (wt) {
13101 httime = tctime;
13102 htleve = val;
13103 } else {
13104 lttime = tctime;
13105 ltleve = val;
13106 }
13107
13108 // draw the tide rectangle:
13109
13110 // tide icon rectangle has default pre-scaled width = 12 ,
13111 // height = 45
13112 int width = (int)(12 * scale_factor + 0.5);
13113 int height = (int)(45 * scale_factor + 0.5);
13114 int linew = wxMax(1, (int)(scale_factor));
13115 int xDraw = r.x - (width / 2);
13116 int yDraw = r.y - (height / 2);
13117
13118 // process tide state ( %height and flow sens )
13119 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13120 int hs = (httime > lttime) ? -4 : 4;
13121 hs *= (int)(scale_factor + 0.5);
13122 if (ts > 0.995 || ts < 0.005) hs = 0;
13123 int ht_y = (int)(height * ts);
13124
13125 // draw yellow tide rectangle outlined in black
13126 pblack_pen->SetWidth(linew);
13127 dc.SetPen(*pblack_pen);
13128 dc.SetBrush(*pyelo_brush);
13129 dc.DrawRectangle(xDraw, yDraw, width, height);
13130
13131 // draw blue rectangle as water height, smaller in width than
13132 // yellow rectangle
13133 dc.SetPen(*pblue_pen);
13134 dc.SetBrush(*pblue_brush);
13135 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13136 (width - (4 * linew)), height - ht_y);
13137
13138 // draw sens arrows (ensure they are not "under-drawn" by top
13139 // line of blue rectangle )
13140 int hl;
13141 wxPoint arrow[3];
13142 arrow[0].x = xDraw + 2 * linew;
13143 arrow[1].x = xDraw + width / 2;
13144 arrow[2].x = xDraw + width - 2 * linew;
13145 pyelo_pen->SetWidth(linew);
13146 pblue_pen->SetWidth(linew);
13147 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13148 {
13149 hl = (int)(height * 0.25) + yDraw;
13150 arrow[0].y = hl;
13151 arrow[1].y = hl + hs;
13152 arrow[2].y = hl;
13153 if (ts < 0.15)
13154 dc.SetPen(*pyelo_pen);
13155 else
13156 dc.SetPen(*pblue_pen);
13157 dc.DrawLines(3, arrow);
13158 }
13159 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13160 {
13161 hl = (int)(height * 0.5) + yDraw;
13162 arrow[0].y = hl;
13163 arrow[1].y = hl + hs;
13164 arrow[2].y = hl;
13165 if (ts < 0.40)
13166 dc.SetPen(*pyelo_pen);
13167 else
13168 dc.SetPen(*pblue_pen);
13169 dc.DrawLines(3, arrow);
13170 }
13171 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13172 {
13173 hl = (int)(height * 0.75) + yDraw;
13174 arrow[0].y = hl;
13175 arrow[1].y = hl + hs;
13176 arrow[2].y = hl;
13177 if (ts < 0.65)
13178 dc.SetPen(*pyelo_pen);
13179 else
13180 dc.SetPen(*pblue_pen);
13181 dc.DrawLines(3, arrow);
13182 }
13183 // draw tide level text
13184 wxString s;
13185 s.Printf(_T("%3.1f"), nowlev);
13186 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13187 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13188 int wx1;
13189 dc.GetTextExtent(s, &wx1, NULL);
13190 wx1 *= g_Platform->GetDisplayDIPMult(this);
13191 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13192 }
13193 }
13194 }
13195 }
13196 }
13197 }
13198 }
13199 }
13200}
13201
13202//------------------------------------------------------------------------------------------
13203// Currents Support
13204//------------------------------------------------------------------------------------------
13205
13206void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13207 if (!ptcmgr) return;
13208
13209 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13210
13211 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13212 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13213 double lon = pIDX->IDX_lon;
13214 double lat = pIDX->IDX_lat;
13215
13216 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13217 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13218 if ((BBox.Contains(lat, lon))) {
13219 // Manage the point selection list
13220 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13221 }
13222 }
13223 }
13224}
13225
13226void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13227 if (!ptcmgr) return;
13228
13229 float tcvalue, dir;
13230 bool bnew_val;
13231 char sbuf[20];
13232 wxFont *pTCFont;
13233 double lon_last = 0.;
13234 double lat_last = 0.;
13235 // arrow size for Raz Blanchard : 12 knots north
13236 double marge = 0.2;
13237 bool cur_time = !gTimeSource.IsValid();
13238
13239 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13240 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13241
13242 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
13243 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
13244 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13245 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )), 1,
13246 wxPENSTYLE_SOLID);
13247 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13248 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )),
13249 wxBRUSHSTYLE_SOLID);
13250 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13251 GetGlobalColor(_T ( "UIBDR" )), wxBRUSHSTYLE_SOLID);
13252 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13253 GetGlobalColor(_T ( "UINFD" )), wxBRUSHSTYLE_SOLID);
13254
13255 double skew_angle = GetVPRotation();
13256
13257 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13258 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13259 int font_size = wxMax(10, dFont->GetPointSize());
13260 font_size /= g_Platform->GetDisplayDIPMult(this);
13261 pTCFont = FontMgr::Get().FindOrCreateFont(
13262 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13263 false, dFont->GetFaceName());
13264
13265 float scale_factor = 1.0;
13266
13267 // Set the onscreen size of the symbol
13268 // Compensate for various display resolutions
13269
13270#if 0
13271 float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *3 / 1000; // Intended physical rendered size onscreen
13272 nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 2);
13273 nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 4);
13274 float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
13275#endif
13276
13277#if 0
13278 // another method is simply to declare that the icon shall be x times the size of a raster symbol (e.g.BOYLAT)
13279 // This is a bit of a hack that will suffice until until we get fully scalable ENC symbol sets
13280 float nominal_icon_size_pixels = 6; // 16 / 3
13281 float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
13282#endif
13283
13284#ifndef __ANDROID__
13285 // or, x times size of text font
13286 wxScreenDC sdc;
13287 int height;
13288 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13289 height *= g_Platform->GetDisplayDIPMult(this);
13290 float nominal_icon_size_pixels = 15;
13291 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13292
13293#else
13294 // Yet another method goes like this:
13295 // Set the onscreen size of the symbol
13296 // Compensate for various display resolutions
13297 // Develop empirically....
13298 float icon_pixelRefDim = 5;
13299
13300 double symHeight =
13301 icon_pixelRefDim /
13302 GetPixPerMM(); // from draw instructions, symbol is xx pix high
13303 double targetHeight0 = 2.0;
13304
13305 // But we want to scale the size down for smaller displays
13306 double displaySize = m_display_size_mm;
13307 displaySize = wxMax(displaySize, 100);
13308
13309 float targetHeight = wxMin(targetHeight0, displaySize / 50);
13310 double pix_factor = targetHeight / symHeight;
13311#endif
13312
13313 scale_factor *= pix_factor;
13314
13315 float user_scale_factor = g_ChartScaleFactorExp;
13316 if (g_ChartScaleFactorExp > 1.0)
13317 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13318 1.2; // soften the scale factor a bit
13319
13320 scale_factor *= user_scale_factor;
13321
13322 scale_factor *= GetContentScaleFactor();
13323
13324 {
13325 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13326 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13327 double lon = pIDX->IDX_lon;
13328 double lat = pIDX->IDX_lat;
13329
13330 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13331 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13332 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13333 wxPoint r;
13334 GetCanvasPointPix(lat, lon, &r);
13335
13336 wxPoint d[4]; // points of a diamond at the current station location
13337 int dd = (int)(5.0 * scale_factor + 0.5);
13338 d[0].x = r.x;
13339 d[0].y = r.y + dd;
13340 d[1].x = r.x + dd;
13341 d[1].y = r.y;
13342 d[2].x = r.x;
13343 d[2].y = r.y - dd;
13344 d[3].x = r.x - dd;
13345 d[3].y = r.y;
13346
13347 if (1) {
13348 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13349 dc.SetPen(*pblack_pen);
13350 dc.SetBrush(*porange_brush);
13351 dc.DrawPolygon(4, d);
13352
13353 if (type == 'C') {
13354 dc.SetBrush(*pblack_brush);
13355 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13356 }
13357
13358 if (GetVP().chart_scale < 1000000) {
13359 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13360 continue;
13361 } else
13362 continue;
13363
13364 if (1 /*type == 'c'*/) {
13365 {
13366 // Get the display pixel location of the current station
13367 int pixxc, pixyc;
13368 pixxc = r.x;
13369 pixyc = r.y;
13370
13371 // Adjust drawing size using logarithmic scale. tcvalue is
13372 // current in knots
13373 double a1 = fabs(tcvalue) * 10.;
13374 // Current values <= 0.1 knot will have no arrow
13375 a1 = wxMax(1.0, a1);
13376 double a2 = log10(a1);
13377
13378 float cscale = scale_factor * a2 * 0.4;
13379
13380 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13381 dc.SetPen(*porange_pen);
13382 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13383 cscale);
13384 // Draw text, if enabled
13385
13386 if (bDrawCurrentValues) {
13387 dc.SetFont(*pTCFont);
13388 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13389 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13390 }
13391 }
13392 } // scale
13393 }
13394 /* This is useful for debugging the TC database
13395 else
13396 {
13397 dc.SetPen ( *porange_pen );
13398 dc.SetBrush ( *pgray_brush );
13399 dc.DrawPolygon ( 4, d );
13400 }
13401 */
13402 }
13403 lon_last = lon;
13404 lat_last = lat;
13405 }
13406 }
13407 }
13408}
13409
13410void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13411 pCwin = new TCWin(this, x, y, pvIDX);
13412}
13413
13414#define NUM_CURRENT_ARROW_POINTS 9
13415static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13416 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13417 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13418 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13419
13420void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13421 double scale) {
13422 if (scale > 1e-2) {
13423 float sin_rot = sin(rot_angle * PI / 180.);
13424 float cos_rot = cos(rot_angle * PI / 180.);
13425
13426 // Move to the first point
13427
13428 float xt = CurrentArrowArray[0].x;
13429 float yt = CurrentArrowArray[0].y;
13430
13431 float xp = (xt * cos_rot) - (yt * sin_rot);
13432 float yp = (xt * sin_rot) + (yt * cos_rot);
13433 int x1 = (int)(xp * scale);
13434 int y1 = (int)(yp * scale);
13435
13436 // Walk thru the point list
13437 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13438 xt = CurrentArrowArray[ip].x;
13439 yt = CurrentArrowArray[ip].y;
13440
13441 float xp = (xt * cos_rot) - (yt * sin_rot);
13442 float yp = (xt * sin_rot) + (yt * cos_rot);
13443 int x2 = (int)(xp * scale);
13444 int y2 = (int)(yp * scale);
13445
13446 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13447
13448 x1 = x2;
13449 y1 = y2;
13450 }
13451 }
13452}
13453
13454wxString ChartCanvas::FindValidUploadPort() {
13455 wxString port;
13456 // Try to use the saved persistent upload port first
13457 if (!g_uploadConnection.IsEmpty() &&
13458 g_uploadConnection.StartsWith(_T("Serial"))) {
13459 port = g_uploadConnection;
13460 }
13461
13462 else {
13463 // If there is no persistent upload port recorded (yet)
13464 // then use the first available serial connection which has output defined.
13465 for (auto *cp : TheConnectionParams()) {
13466 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13467 port << _T("Serial:") << cp->Port;
13468 }
13469 }
13470 return port;
13471}
13472
13473void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13474 if (!win) return;
13475
13476 if (NULL == g_pais_query_dialog_active) {
13477 int pos_x = g_ais_query_dialog_x;
13478 int pos_y = g_ais_query_dialog_y;
13479
13480 if (g_pais_query_dialog_active) {
13481 g_pais_query_dialog_active->Destroy();
13482 g_pais_query_dialog_active = new AISTargetQueryDialog();
13483 } else {
13484 g_pais_query_dialog_active = new AISTargetQueryDialog();
13485 }
13486
13487 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13488 wxPoint(pos_x, pos_y));
13489
13490 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13491 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13492 g_pais_query_dialog_active->SetMMSI(mmsi);
13493 g_pais_query_dialog_active->UpdateText();
13494 wxSize sz = g_pais_query_dialog_active->GetSize();
13495
13496 bool b_reset_pos = false;
13497#ifdef __WXMSW__
13498 // Support MultiMonitor setups which an allow negative window positions.
13499 // If the requested window title bar does not intersect any installed
13500 // monitor, then default to simple primary monitor positioning.
13501 RECT frame_title_rect;
13502 frame_title_rect.left = pos_x;
13503 frame_title_rect.top = pos_y;
13504 frame_title_rect.right = pos_x + sz.x;
13505 frame_title_rect.bottom = pos_y + 30;
13506
13507 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13508 b_reset_pos = true;
13509#else
13510
13511 // Make sure drag bar (title bar) of window intersects wxClient Area of
13512 // screen, with a little slop...
13513 wxRect window_title_rect; // conservative estimate
13514 window_title_rect.x = pos_x;
13515 window_title_rect.y = pos_y;
13516 window_title_rect.width = sz.x;
13517 window_title_rect.height = 30;
13518
13519 wxRect ClientRect = wxGetClientDisplayRect();
13520 ClientRect.Deflate(
13521 60, 60); // Prevent the new window from being too close to the edge
13522 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13523
13524#endif
13525
13526 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13527
13528 } else {
13529 g_pais_query_dialog_active->SetMMSI(mmsi);
13530 g_pais_query_dialog_active->UpdateText();
13531 }
13532
13533 g_pais_query_dialog_active->Show();
13534}
13535
13536void ChartCanvas::ToggleCanvasQuiltMode(void) {
13537 bool cur_mode = GetQuiltMode();
13538
13539 if (!GetQuiltMode())
13540 SetQuiltMode(true);
13541 else if (GetQuiltMode()) {
13542 SetQuiltMode(false);
13543 g_sticky_chart = GetQuiltReferenceChartIndex();
13544 }
13545
13546 if (cur_mode != GetQuiltMode()) {
13547 SetupCanvasQuiltMode();
13548 DoCanvasUpdate();
13549 InvalidateGL();
13550 Refresh();
13551 }
13552 // TODO What to do about this?
13553 // g_bQuiltEnable = GetQuiltMode();
13554
13555 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13556 if (ps52plib) ps52plib->GenerateStateHash();
13557
13558 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13559 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13560}
13561
13562void ChartCanvas::DoCanvasStackDelta(int direction) {
13563 if (!GetQuiltMode()) {
13564 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13565 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13566 if ((current_stack_index + direction) < 0) return;
13567
13568 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13569 int new_dbIndex =
13570 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13571
13572 if (IsChartQuiltableRef(new_dbIndex)) {
13573 ToggleCanvasQuiltMode();
13574 SelectQuiltRefdbChart(new_dbIndex);
13575 m_bpersistent_quilt = false;
13576 }
13577 } else {
13578 SelectChartFromStack(current_stack_index + direction);
13579 }
13580 } else {
13581 std::vector<int> piano_chart_index_array =
13582 GetQuiltExtendedStackdbIndexArray();
13583 int refdb = GetQuiltRefChartdbIndex();
13584
13585 // Find the ref chart in the stack
13586 int current_index = -1;
13587 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13588 if (refdb == piano_chart_index_array[i]) {
13589 current_index = i;
13590 break;
13591 }
13592 }
13593 if (current_index == -1) return;
13594
13595 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13596 int target_family = ctet.GetChartFamily();
13597
13598 int new_index = -1;
13599 int check_index = current_index + direction;
13600 bool found = false;
13601 int check_dbIndex = -1;
13602 int new_dbIndex = -1;
13603
13604 // When quilted. switch within the same chart family
13605 while (!found &&
13606 (unsigned int)check_index < piano_chart_index_array.size() &&
13607 (check_index >= 0)) {
13608 check_dbIndex = piano_chart_index_array[check_index];
13609 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13610 if (target_family == cte.GetChartFamily()) {
13611 found = true;
13612 new_index = check_index;
13613 new_dbIndex = check_dbIndex;
13614 break;
13615 }
13616
13617 check_index += direction;
13618 }
13619
13620 if (!found) return;
13621
13622 if (!IsChartQuiltableRef(new_dbIndex)) {
13623 ToggleCanvasQuiltMode();
13624 SelectdbChart(new_dbIndex);
13625 m_bpersistent_quilt = true;
13626 } else {
13627 SelectQuiltRefChart(new_index);
13628 }
13629 }
13630
13631 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13632 // (checkmarks etc)
13633 SetQuiltChartHiLiteIndex(-1);
13634
13635 ReloadVP();
13636}
13637
13638//--------------------------------------------------------------------------------------------------------
13639//
13640// Toolbar support
13641//
13642//--------------------------------------------------------------------------------------------------------
13643
13644void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13645 // Handle the per-canvas toolbar clicks here
13646
13647 switch (event.GetId()) {
13648 case ID_ZOOMIN: {
13649 StopMovement();
13650 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13651 break;
13652 }
13653
13654 case ID_ZOOMOUT: {
13655 StopMovement();
13656 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13657 break;
13658 }
13659
13660 case ID_STKUP:
13661 DoCanvasStackDelta(1);
13662 DoCanvasUpdate();
13663 break;
13664
13665 case ID_STKDN:
13666 DoCanvasStackDelta(-1);
13667 DoCanvasUpdate();
13668 break;
13669
13670 case ID_FOLLOW: {
13671 TogglebFollow();
13672 break;
13673 }
13674
13675 case ID_CURRENT: {
13676 ShowCurrents(!GetbShowCurrent());
13677 ReloadVP();
13678 Refresh(false);
13679 break;
13680 }
13681
13682 case ID_TIDE: {
13683 ShowTides(!GetbShowTide());
13684 ReloadVP();
13685 Refresh(false);
13686 break;
13687 }
13688
13689 case ID_ROUTE: {
13690 if (0 == m_routeState) {
13691 StartRoute();
13692 } else {
13693 FinishRoute();
13694 }
13695
13696#ifdef __ANDROID__
13697 androidSetRouteAnnunciator(m_routeState == 1);
13698#endif
13699 break;
13700 }
13701
13702 case ID_AIS: {
13703 SetAISCanvasDisplayStyle(-1);
13704 break;
13705 }
13706
13707 default:
13708 break;
13709 }
13710
13711 // And then let gFrame handle the rest....
13712 event.Skip();
13713}
13714
13715void ChartCanvas::SetShowAIS(bool show) {
13716 m_bShowAIS = show;
13717 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13718 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13719}
13720
13721void ChartCanvas::SetAttenAIS(bool show) {
13722 m_bShowAISScaled = show;
13723 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13724 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13725}
13726
13727void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
13728 // make some arrays to hold the dfferences between cycle steps
13729 // show all, scaled, hide all
13730 bool bShowAIS_Array[3] = {true, true, false};
13731 bool bShowScaled_Array[3] = {false, true, true};
13732 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
13733 _("Attenuate less critical AIS targets"),
13734 _("Hide AIS Targets")};
13735 wxString iconName_Array[3] = {_T("AIS"), _T("AIS_Suppressed"),
13736 _T("AIS_Disabled")};
13737 int ArraySize = 3;
13738 int AIS_Toolbar_Switch = 0;
13739 if (StyleIndx == -1) { // -1 means coming from toolbar button
13740 // find current state of switch
13741 for (int i = 1; i < ArraySize; i++) {
13742 if ((bShowAIS_Array[i] == m_bShowAIS) &&
13743 (bShowScaled_Array[i] == m_bShowAISScaled))
13744 AIS_Toolbar_Switch = i;
13745 }
13746 AIS_Toolbar_Switch++; // we did click so continu with next item
13747 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
13748 AIS_Toolbar_Switch++;
13749
13750 } else { // coming from menu bar.
13751 AIS_Toolbar_Switch = StyleIndx;
13752 }
13753 // make sure we are not above array
13754 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
13755
13756 int AIS_Toolbar_Switch_Next =
13757 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
13758 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
13759 AIS_Toolbar_Switch_Next++;
13760 if (AIS_Toolbar_Switch_Next >= ArraySize)
13761 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
13762
13763 // Set found values to global and member variables
13764 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
13765 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
13766}
13767
13768void ChartCanvas::TouchAISToolActive(void) {}
13769
13770void ChartCanvas::UpdateAISTBTool(void) {}
13771
13772//---------------------------------------------------------------------------------
13773//
13774// Compass/GPS status icon support
13775//
13776//---------------------------------------------------------------------------------
13777
13778void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
13779 // Look for change in overlap or positions
13780 bool b_update = false;
13781 int cc1_edge_comp = 2;
13782 wxRect rect = m_Compass->GetRect();
13783 wxSize parent_size = GetSize();
13784
13785 parent_size *= m_displayScale;
13786
13787 // check to see if it would overlap if it was in its home position (upper
13788 // right)
13789 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
13790 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
13791 wxRect compass_rect(compass_pt, rect.GetSize());
13792
13793 m_Compass->Move(compass_pt);
13794
13795 if (m_Compass && m_Compass->IsShown())
13796 m_Compass->UpdateStatus(b_force_new | b_update);
13797
13798 wxPoint note_point =
13799 wxPoint(compass_rect.x - compass_rect.width, compass_rect.y);
13800 m_notification_button->Move(note_point);
13801
13802 m_notification_button->UpdateStatus();
13803 if (b_force_new | b_update) Refresh();
13804}
13805
13806void ChartCanvas::SelectChartFromStack(int index, bool bDir,
13807 ChartTypeEnum New_Type,
13808 ChartFamilyEnum New_Family) {
13809 if (!GetpCurrentStack()) return;
13810 if (!ChartData) return;
13811
13812 if (index < GetpCurrentStack()->nEntry) {
13813 // Open the new chart
13814 ChartBase *pTentative_Chart;
13815 pTentative_Chart = ChartData->OpenStackChartConditional(
13816 GetpCurrentStack(), index, bDir, New_Type, New_Family);
13817
13818 if (pTentative_Chart) {
13819 if (m_singleChart) m_singleChart->Deactivate();
13820
13821 m_singleChart = pTentative_Chart;
13822 m_singleChart->Activate();
13823
13824 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13825 GetpCurrentStack(), m_singleChart->GetFullPath());
13826 }
13827
13828 // Setup the view
13829 double zLat, zLon;
13830 if (m_bFollow) {
13831 zLat = gLat;
13832 zLon = gLon;
13833 } else {
13834 zLat = m_vLat;
13835 zLon = m_vLon;
13836 }
13837
13838 double best_scale_ppm = GetBestVPScale(m_singleChart);
13839 double rotation = GetVPRotation();
13840 double oldskew = GetVPSkew();
13841 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
13842
13843 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
13844 if (fabs(oldskew) > 0.0001) rotation = 0.0;
13845 if (fabs(newskew) > 0.0001) rotation = newskew;
13846 }
13847
13848 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
13849
13850 UpdateGPSCompassStatusBox(true); // Pick up the rotation
13851 }
13852
13853 // refresh Piano
13854 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
13855 if (idx < 0) return;
13856
13857 std::vector<int> piano_active_chart_index_array;
13858 piano_active_chart_index_array.push_back(
13859 GetpCurrentStack()->GetCurrentEntrydbIndex());
13860 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
13861}
13862
13863void ChartCanvas::SelectdbChart(int dbindex) {
13864 if (!GetpCurrentStack()) return;
13865 if (!ChartData) return;
13866
13867 if (dbindex >= 0) {
13868 // Open the new chart
13869 ChartBase *pTentative_Chart;
13870 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
13871
13872 if (pTentative_Chart) {
13873 if (m_singleChart) m_singleChart->Deactivate();
13874
13875 m_singleChart = pTentative_Chart;
13876 m_singleChart->Activate();
13877
13878 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13879 GetpCurrentStack(), m_singleChart->GetFullPath());
13880 }
13881
13882 // Setup the view
13883 double zLat, zLon;
13884 if (m_bFollow) {
13885 zLat = gLat;
13886 zLon = gLon;
13887 } else {
13888 zLat = m_vLat;
13889 zLon = m_vLon;
13890 }
13891
13892 double best_scale_ppm = GetBestVPScale(m_singleChart);
13893
13894 if (m_singleChart)
13895 SetViewPoint(zLat, zLon, best_scale_ppm,
13896 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
13897
13898 // SetChartUpdatePeriod( );
13899
13900 // UpdateGPSCompassStatusBox(); // Pick up the rotation
13901 }
13902
13903 // TODO refresh_Piano();
13904}
13905
13906void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
13907 double target_scale = GetVP().view_scale_ppm;
13908
13909 if (!GetQuiltMode()) {
13910 if (GetpCurrentStack()) {
13911 int stack_index = -1;
13912 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
13913 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
13914 if (check_dbIndex < 0) continue;
13915 const ChartTableEntry &cte =
13916 ChartData->GetChartTableEntry(check_dbIndex);
13917 if (type == cte.GetChartType()) {
13918 stack_index = i;
13919 break;
13920 } else if (family == cte.GetChartFamily()) {
13921 stack_index = i;
13922 break;
13923 }
13924 }
13925
13926 if (stack_index >= 0) {
13927 SelectChartFromStack(stack_index);
13928 }
13929 }
13930 } else {
13931 int sel_dbIndex = -1;
13932 std::vector<int> piano_chart_index_array =
13933 GetQuiltExtendedStackdbIndexArray();
13934 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13935 int check_dbIndex = piano_chart_index_array[i];
13936 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13937 if (type == cte.GetChartType()) {
13938 if (IsChartQuiltableRef(check_dbIndex)) {
13939 sel_dbIndex = check_dbIndex;
13940 break;
13941 }
13942 } else if (family == cte.GetChartFamily()) {
13943 if (IsChartQuiltableRef(check_dbIndex)) {
13944 sel_dbIndex = check_dbIndex;
13945 break;
13946 }
13947 }
13948 }
13949
13950 if (sel_dbIndex >= 0) {
13951 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
13952 // Re-qualify the quilt reference chart selection
13953 AdjustQuiltRefChart();
13954 }
13955
13956 // Now reset the scale to the target...
13957 SetVPScale(target_scale);
13958 }
13959
13960 SetQuiltChartHiLiteIndex(-1);
13961
13962 ReloadVP();
13963}
13964
13965bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
13966 return std::find(m_tile_yesshow_index_array.begin(),
13967 m_tile_yesshow_index_array.end(),
13968 index) != m_tile_yesshow_index_array.end();
13969}
13970
13971bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
13972 return std::find(m_tile_noshow_index_array.begin(),
13973 m_tile_noshow_index_array.end(),
13974 index) != m_tile_noshow_index_array.end();
13975}
13976
13977void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
13978 if (std::find(m_tile_noshow_index_array.begin(),
13979 m_tile_noshow_index_array.end(),
13980 index) == m_tile_noshow_index_array.end()) {
13981 m_tile_noshow_index_array.push_back(index);
13982 }
13983}
13984
13985//-------------------------------------------------------------------------------------------------------
13986//
13987// Piano support
13988//
13989//-------------------------------------------------------------------------------------------------------
13990
13991void ChartCanvas::HandlePianoClick(
13992 int selected_index, const std::vector<int> &selected_dbIndex_array) {
13993 if (g_options && g_options->IsShown())
13994 return; // Piano might be invalid due to chartset updates.
13995 if (!m_pCurrentStack) return;
13996 if (!ChartData) return;
13997
13998 // stop movement or on slow computer we may get something like :
13999 // zoom out with the wheel (timer is set)
14000 // quickly click and display a chart, which may zoom in
14001 // but the delayed timer fires first and it zooms out again!
14002 StopMovement();
14003
14004 // When switching by piano key click, we may appoint the new target chart to
14005 // be any chart in the composite array.
14006 // As an improvement to UX, find the chart that is "closest" to the current
14007 // vp,
14008 // and select that chart. This will cause a jump to the centroid of that
14009 // chart
14010
14011 double distance = 25000; // RTW
14012 int closest_index = -1;
14013 for (int chart_index : selected_dbIndex_array) {
14014 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14015 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14016 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14017
14018 // measure distance as Manhattan style
14019 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14020 if (test_distance < distance) {
14021 distance = test_distance;
14022 closest_index = chart_index;
14023 }
14024 }
14025
14026 int selected_dbIndex = selected_dbIndex_array[0];
14027 if (closest_index >= 0) selected_dbIndex = closest_index;
14028
14029 if (!GetQuiltMode()) {
14030 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14031 if (IsChartQuiltableRef(selected_dbIndex)) {
14032 ToggleCanvasQuiltMode();
14033 SelectQuiltRefdbChart(selected_dbIndex);
14034 m_bpersistent_quilt = false;
14035 } else {
14036 SelectChartFromStack(selected_index);
14037 }
14038 } else {
14039 SelectChartFromStack(selected_index);
14040 g_sticky_chart = selected_dbIndex;
14041 }
14042
14043 if (m_singleChart)
14044 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14045 } else {
14046 // Handle MBTiles overlays first
14047 // Left click simply toggles the noshow array index entry
14048 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14049 bool bfound = false;
14050 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14051 if (m_tile_noshow_index_array[i] ==
14052 selected_dbIndex) { // chart is in the noshow list
14053 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14054 i); // erase it
14055 bfound = true;
14056 break;
14057 }
14058 }
14059 if (!bfound) {
14060 m_tile_noshow_index_array.push_back(selected_dbIndex);
14061 }
14062
14063 // If not already present, add this tileset to the "yes_show" array.
14064 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14065 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14066 }
14067
14068 else {
14069 if (IsChartQuiltableRef(selected_dbIndex)) {
14070 // if( ChartData ) ChartData->PurgeCache();
14071
14072 // If the chart is a vector chart, and of very large scale,
14073 // then we had better set the new scale directly to avoid excessive
14074 // underzoom on, eg, Inland ENCs
14075 bool set_scale = false;
14076 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14077 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14078 set_scale = true;
14079 }
14080 }
14081
14082 if (!set_scale) {
14083 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14084 } else {
14085 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14086
14087 // Adjust scale so that the selected chart is underzoomed/overzoomed
14088 // by a controlled amount
14089 ChartBase *pc =
14090 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14091 if (pc) {
14092 double proposed_scale_onscreen =
14094
14095 if (g_bPreserveScaleOnX) {
14096 proposed_scale_onscreen =
14097 wxMin(proposed_scale_onscreen,
14098 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14099 GetCanvasWidth()));
14100 } else {
14101 proposed_scale_onscreen =
14102 wxMin(proposed_scale_onscreen,
14103 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14104 GetCanvasWidth()));
14105
14106 proposed_scale_onscreen =
14107 wxMax(proposed_scale_onscreen,
14108 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14109 g_b_overzoom_x));
14110 }
14111
14112 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14113 }
14114 }
14115 } else {
14116 ToggleCanvasQuiltMode();
14117 SelectdbChart(selected_dbIndex);
14118 m_bpersistent_quilt = true;
14119 }
14120 }
14121 }
14122
14123 SetQuiltChartHiLiteIndex(-1);
14124 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14125 // (checkmarks etc)
14126 HideChartInfoWindow();
14127 DoCanvasUpdate();
14128 ReloadVP(); // Pick up the new selections
14129}
14130
14131void ChartCanvas::HandlePianoRClick(
14132 int x, int y, int selected_index,
14133 const std::vector<int> &selected_dbIndex_array) {
14134 if (g_options && g_options->IsShown())
14135 return; // Piano might be invalid due to chartset updates.
14136 if (!GetpCurrentStack()) return;
14137
14138 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14139 UpdateCanvasControlBar();
14140
14141 SetQuiltChartHiLiteIndex(-1);
14142}
14143
14144void ChartCanvas::HandlePianoRollover(
14145 int selected_index, const std::vector<int> &selected_dbIndex_array,
14146 int n_charts, int scale) {
14147 if (g_options && g_options->IsShown())
14148 return; // Piano might be invalid due to chartset updates.
14149 if (!GetpCurrentStack()) return;
14150 if (!ChartData) return;
14151
14152 if (ChartData->IsBusy()) return;
14153
14154 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14155
14156 if (!GetQuiltMode()) {
14157 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14158 } else {
14159 // Select the correct vector
14160 std::vector<int> piano_chart_index_array;
14161 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14162 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14163 if ((GetpCurrentStack()->nEntry > 1) ||
14164 (piano_chart_index_array.size() >= 1)) {
14165 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14166
14167 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14168 ReloadVP(false); // no VP adjustment allowed
14169 } else if (GetpCurrentStack()->nEntry == 1) {
14170 const ChartTableEntry &cte =
14171 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14172 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14173 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14174 ReloadVP(false);
14175 } else if ((-1 == selected_index) &&
14176 (0 == selected_dbIndex_array.size())) {
14177 ShowChartInfoWindow(key_location.x, -1);
14178 }
14179 }
14180 } else {
14181 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14182
14183 if ((GetpCurrentStack()->nEntry > 1) ||
14184 (piano_chart_index_array.size() >= 1)) {
14185 if (n_charts > 1)
14186 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14187 selected_dbIndex_array);
14188 else if (n_charts == 1)
14189 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14190
14191 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14192 ReloadVP(false); // no VP adjustment allowed
14193 }
14194 }
14195 }
14196}
14197
14198void ChartCanvas::ClearPianoRollover() {
14199 ClearQuiltChartHiLiteIndexArray();
14200 ShowChartInfoWindow(0, -1);
14201 std::vector<int> vec;
14202 ShowCompositeInfoWindow(0, 0, 0, vec);
14203 ReloadVP(false);
14204}
14205
14206void ChartCanvas::UpdateCanvasControlBar(void) {
14207 if (m_pianoFrozen) return;
14208
14209 if (!GetpCurrentStack()) return;
14210 if (!ChartData) return;
14211 if (!g_bShowChartBar) return;
14212
14213 int sel_type = -1;
14214 int sel_family = -1;
14215
14216 std::vector<int> piano_chart_index_array;
14217 std::vector<int> empty_piano_chart_index_array;
14218
14219 wxString old_hash = m_Piano->GetStoredHash();
14220
14221 if (GetQuiltMode()) {
14222 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14223 GetQuiltFullScreendbIndexArray());
14224
14225 std::vector<int> piano_active_chart_index_array =
14226 GetQuiltCandidatedbIndexArray();
14227 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14228
14229 std::vector<int> piano_eclipsed_chart_index_array =
14230 GetQuiltEclipsedStackdbIndexArray();
14231 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14232
14233 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14234 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14235
14236 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14237 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14238 } else {
14239 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14240 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14241 // TODO refresh_Piano();
14242
14243 if (m_singleChart) {
14244 sel_type = m_singleChart->GetChartType();
14245 sel_family = m_singleChart->GetChartFamily();
14246 }
14247 }
14248
14249 // Set up the TMerc and Skew arrays
14250 std::vector<int> piano_skew_chart_index_array;
14251 std::vector<int> piano_tmerc_chart_index_array;
14252 std::vector<int> piano_poly_chart_index_array;
14253
14254 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14255 const ChartTableEntry &ctei =
14256 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14257 double skew_norm = ctei.GetChartSkew();
14258 if (skew_norm > 180.) skew_norm -= 360.;
14259
14260 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14261 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14262
14263 // Polyconic skewed charts should show as skewed
14264 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14265 if (fabs(skew_norm) > 1.)
14266 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14267 else
14268 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14269 } else if (fabs(skew_norm) > 1.)
14270 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14271 }
14272 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14273 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14274 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14275
14276 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14277 if (new_hash != old_hash) {
14278 m_Piano->FormatKeys();
14279 HideChartInfoWindow();
14280 m_Piano->ResetRollover();
14281 SetQuiltChartHiLiteIndex(-1);
14282 m_brepaint_piano = true;
14283 }
14284
14285 // Create a bitmask int that describes what Family/Type of charts are shown in
14286 // the bar, and notify the platform.
14287 int mask = 0;
14288 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14289 const ChartTableEntry &ctei =
14290 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14291 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14292 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14293 if (e == CHART_FAMILY_RASTER) mask |= 1;
14294 if (e == CHART_FAMILY_VECTOR) {
14295 if (t == CHART_TYPE_CM93COMP)
14296 mask |= 4;
14297 else
14298 mask |= 2;
14299 }
14300 }
14301
14302 wxString s_indicated;
14303 if (sel_type == CHART_TYPE_CM93COMP)
14304 s_indicated = _T("cm93");
14305 else {
14306 if (sel_family == CHART_FAMILY_RASTER)
14307 s_indicated = _T("raster");
14308 else if (sel_family == CHART_FAMILY_VECTOR)
14309 s_indicated = _T("vector");
14310 }
14311
14312 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14313}
14314
14315void ChartCanvas::FormatPianoKeys(void) { m_Piano->FormatKeys(); }
14316
14317void ChartCanvas::PianoPopupMenu(
14318 int x, int y, int selected_index,
14319 const std::vector<int> &selected_dbIndex_array) {
14320 if (!GetpCurrentStack()) return;
14321
14322 // No context menu if quilting is disabled
14323 if (!GetQuiltMode()) return;
14324
14325 m_piano_ctx_menu = new wxMenu();
14326
14327 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14328 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14329 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14330 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14331 } else {
14332 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14333 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14334 // wxEVT_COMMAND_MENU_SELECTED,
14335 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14336
14337 menu_selected_dbIndex = selected_dbIndex_array[0];
14338 menu_selected_index = selected_index;
14339
14340 // Search the no-show array
14341 bool b_is_in_noshow = false;
14342 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14343 if (m_quilt_noshow_index_array[i] ==
14344 menu_selected_dbIndex) // chart is in the noshow list
14345 {
14346 b_is_in_noshow = true;
14347 break;
14348 }
14349 }
14350
14351 if (b_is_in_noshow) {
14352 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14353 _("Show This Chart"));
14354 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14355 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14356 } else if (GetpCurrentStack()->nEntry > 1) {
14357 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14358 _("Hide This Chart"));
14359 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14360 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14361 }
14362 }
14363
14364 wxPoint pos = wxPoint(x, y - 30);
14365
14366 // Invoke the drop-down menu
14367 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14368 PopupMenu(m_piano_ctx_menu, pos);
14369
14370 delete m_piano_ctx_menu;
14371 m_piano_ctx_menu = NULL;
14372
14373 HideChartInfoWindow();
14374 m_Piano->ResetRollover();
14375
14376 SetQuiltChartHiLiteIndex(-1);
14377 ClearQuiltChartHiLiteIndexArray();
14378
14379 ReloadVP();
14380}
14381
14382void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14383 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14384 if (m_quilt_noshow_index_array[i] ==
14385 menu_selected_dbIndex) // chart is in the noshow list
14386 {
14387 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14388 break;
14389 }
14390 }
14391}
14392
14393void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14394 if (!GetpCurrentStack()) return;
14395 if (!ChartData) return;
14396
14397 RemoveChartFromQuilt(menu_selected_dbIndex);
14398
14399 // It could happen that the chart being disabled is the reference
14400 // chart....
14401 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14402 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14403
14404 int i = menu_selected_index + 1; // select next smaller scale chart
14405 bool b_success = false;
14406 while (i < GetpCurrentStack()->nEntry - 1) {
14407 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14408 if (type == ChartData->GetDBChartType(dbIndex)) {
14409 SelectQuiltRefChart(i);
14410 b_success = true;
14411 break;
14412 }
14413 i++;
14414 }
14415
14416 // If that did not work, try to select the next larger scale compatible
14417 // chart
14418 if (!b_success) {
14419 i = menu_selected_index - 1;
14420 while (i > 0) {
14421 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14422 if (type == ChartData->GetDBChartType(dbIndex)) {
14423 SelectQuiltRefChart(i);
14424 b_success = true;
14425 break;
14426 }
14427 i--;
14428 }
14429 }
14430 }
14431}
14432
14433void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14434 // Remove the item from the list (if it appears) to avoid multiple addition
14435 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14436 if (m_quilt_noshow_index_array[i] ==
14437 dbIndex) // chart is already in the noshow list
14438 {
14439 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14440 break;
14441 }
14442 }
14443
14444 m_quilt_noshow_index_array.push_back(dbIndex);
14445}
14446
14447bool ChartCanvas::UpdateS52State() {
14448 bool retval = false;
14449 // printf(" update %d\n", IsPrimaryCanvas());
14450
14451 if (ps52plib) {
14452 ps52plib->SetShowS57Text(m_encShowText);
14453 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14454 ps52plib->m_bShowSoundg = m_encShowDepth;
14455 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14456 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14457
14458 // Lights
14459 if (!m_encShowLights) // On, going off
14460 ps52plib->AddObjNoshow("LIGHTS");
14461 else // Off, going on
14462 ps52plib->RemoveObjNoshow("LIGHTS");
14463 ps52plib->SetLightsOff(!m_encShowLights);
14464 ps52plib->m_bExtendLightSectors = true;
14465
14466 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14467 ps52plib->SetAnchorOn(m_encShowAnchor);
14468 ps52plib->SetQualityOfData(m_encShowDataQual);
14469 }
14470
14471 return retval;
14472}
14473
14474void ChartCanvas::SetShowENCDataQual(bool show) {
14475 m_encShowDataQual = show;
14476 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14477 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14478
14479 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14480}
14481
14482void ChartCanvas::SetShowENCText(bool show) {
14483 m_encShowText = show;
14484 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14485 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14486
14487 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14488}
14489
14490void ChartCanvas::SetENCDisplayCategory(int category) {
14491 m_encDisplayCategory = category;
14492 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14493}
14494
14495void ChartCanvas::SetShowENCDepth(bool show) {
14496 m_encShowDepth = show;
14497 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14498 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14499
14500 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14501}
14502
14503void ChartCanvas::SetShowENCLightDesc(bool show) {
14504 m_encShowLightDesc = show;
14505 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14506 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14507
14508 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14509}
14510
14511void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14512 m_encShowBuoyLabels = show;
14513 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14514}
14515
14516void ChartCanvas::SetShowENCLights(bool show) {
14517 m_encShowLights = show;
14518 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14519 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14520
14521 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14522}
14523
14524void ChartCanvas::SetShowENCAnchor(bool show) {
14525 m_encShowAnchor = show;
14526 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14527 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14528
14529 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14530}
14531
14532wxRect ChartCanvas::GetMUIBarRect() {
14533 wxRect rv;
14534 if (m_muiBar) {
14535 rv = m_muiBar->GetRect();
14536 }
14537
14538 return rv;
14539}
14540
14541void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14542 if (!GetAlertString().IsEmpty()) {
14543 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14544 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14545
14546 dc.SetFont(*pfont);
14547 dc.SetPen(*wxTRANSPARENT_PEN);
14548
14549 dc.SetBrush(wxColour(243, 229, 47));
14550 int w, h;
14551 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14552 h += 2;
14553 // int yp = vp.pix_height - 20 - h;
14554
14555 wxRect sbr = GetScaleBarRect();
14556 int xp = sbr.x + sbr.width + 10;
14557 int yp = (sbr.y + sbr.height) - h;
14558
14559 int wdraw = w + 10;
14560 dc.DrawRectangle(xp, yp, wdraw, h);
14561 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14562 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14563 }
14564}
14565
14566//--------------------------------------------------------------------------------------------------------
14567// Screen Brightness Control Support Routines
14568//
14569//--------------------------------------------------------------------------------------------------------
14570
14571#ifdef __UNIX__
14572#define BRIGHT_XCALIB
14573#define __OPCPN_USEICC__
14574#endif
14575
14576#ifdef __OPCPN_USEICC__
14577int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14578 double co_green, double co_blue);
14579
14580wxString temp_file_name;
14581#endif
14582
14583#if 0
14584class ocpnCurtain: public wxDialog
14585{
14586 DECLARE_CLASS( ocpnCurtain )
14587 DECLARE_EVENT_TABLE()
14588
14589public:
14590 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14591 ~ocpnCurtain( );
14592 bool ProcessEvent(wxEvent& event);
14593
14594};
14595
14596IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14597
14598BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14599END_EVENT_TABLE()
14600
14601ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14602{
14603 wxDialog::Create( parent, -1, _T("ocpnCurtain"), position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14604}
14605
14606ocpnCurtain::~ocpnCurtain()
14607{
14608}
14609
14610bool ocpnCurtain::ProcessEvent(wxEvent& event)
14611{
14612 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14613 return GetParent()->GetEventHandler()->ProcessEvent(event);
14614}
14615#endif
14616
14617#ifdef _WIN32
14618#include <windows.h>
14619
14620HMODULE hGDI32DLL;
14621typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14622typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14623SetDeviceGammaRamp_ptr_type
14624 g_pSetDeviceGammaRamp; // the API entry points in the dll
14625GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14626
14627WORD *g_pSavedGammaMap;
14628
14629#endif
14630
14631int InitScreenBrightness(void) {
14632#ifdef _WIN32
14633#ifdef ocpnUSE_GL
14634 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14635 HDC hDC;
14636 BOOL bbr;
14637
14638 if (NULL == hGDI32DLL) {
14639 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14640
14641 if (NULL != hGDI32DLL) {
14642 // Get the entry points of the required functions
14643 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14644 hGDI32DLL, "SetDeviceGammaRamp");
14645 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14646 hGDI32DLL, "GetDeviceGammaRamp");
14647
14648 // If the functions are not found, unload the DLL and return false
14649 if ((NULL == g_pSetDeviceGammaRamp) ||
14650 (NULL == g_pGetDeviceGammaRamp)) {
14651 FreeLibrary(hGDI32DLL);
14652 hGDI32DLL = NULL;
14653 return 0;
14654 }
14655 }
14656 }
14657
14658 // Interface is ready, so....
14659 // Get some storage
14660 if (!g_pSavedGammaMap) {
14661 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14662
14663 hDC = GetDC(NULL); // Get the full screen DC
14664 bbr = g_pGetDeviceGammaRamp(
14665 hDC, g_pSavedGammaMap); // Get the existing ramp table
14666 ReleaseDC(NULL, hDC); // Release the DC
14667 }
14668
14669 // On Windows hosts, try to adjust the registry to allow full range
14670 // setting of Gamma table This is an undocumented Windows hack.....
14671 wxRegKey *pRegKey = new wxRegKey(
14672 _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows ")
14673 _T("NT\\CurrentVersion\\ICM"));
14674 if (!pRegKey->Exists()) pRegKey->Create();
14675 pRegKey->SetValue(_T("GdiIcmGammaRange"), 256);
14676
14677 g_brightness_init = true;
14678 return 1;
14679 }
14680#endif
14681
14682 {
14683 if (NULL == g_pcurtain) {
14684 if (gFrame->CanSetTransparent()) {
14685 // Build the curtain window
14686 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, _T(""),
14687 wxPoint(0, 0), ::wxGetDisplaySize(),
14688 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14689 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14690
14691 // g_pcurtain = new ocpnCurtain(gFrame,
14692 // wxPoint(0,0),::wxGetDisplaySize(),
14693 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14694 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14695
14696 g_pcurtain->Hide();
14697
14698 HWND hWnd = GetHwndOf(g_pcurtain);
14699 SetWindowLong(hWnd, GWL_EXSTYLE,
14700 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14701 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14702 g_pcurtain->SetTransparent(0);
14703
14704 g_pcurtain->Maximize();
14705 g_pcurtain->Show();
14706
14707 // All of this is obtuse, but necessary for Windows...
14708 g_pcurtain->Enable();
14709 g_pcurtain->Disable();
14710
14711 gFrame->Disable();
14712 gFrame->Enable();
14713 // SetFocus();
14714 }
14715 }
14716 g_brightness_init = true;
14717
14718 return 1;
14719 }
14720#else
14721 // Look for "xcalib" application
14722 wxString cmd(_T ( "xcalib -version" ));
14723
14724 wxArrayString output;
14725 long r = wxExecute(cmd, output);
14726 if (0 != r)
14727 wxLogMessage(
14728 _T(" External application \"xcalib\" not found. Screen brightness ")
14729 _T("not changed."));
14730
14731 g_brightness_init = true;
14732 return 0;
14733#endif
14734}
14735
14736int RestoreScreenBrightness(void) {
14737#ifdef _WIN32
14738
14739 if (g_pSavedGammaMap) {
14740 HDC hDC = GetDC(NULL); // Get the full screen DC
14741 g_pSetDeviceGammaRamp(hDC,
14742 g_pSavedGammaMap); // Restore the saved ramp table
14743 ReleaseDC(NULL, hDC); // Release the DC
14744
14745 free(g_pSavedGammaMap);
14746 g_pSavedGammaMap = NULL;
14747 }
14748
14749 if (g_pcurtain) {
14750 g_pcurtain->Close();
14751 g_pcurtain->Destroy();
14752 g_pcurtain = NULL;
14753 }
14754
14755 g_brightness_init = false;
14756 return 1;
14757
14758#endif
14759
14760#ifdef BRIGHT_XCALIB
14761 if (g_brightness_init) {
14762 wxString cmd;
14763 cmd = _T("xcalib -clear");
14764 wxExecute(cmd, wxEXEC_ASYNC);
14765 g_brightness_init = false;
14766 }
14767
14768 return 1;
14769#endif
14770
14771 return 0;
14772}
14773
14774// Set brightness. [0..100]
14775int SetScreenBrightness(int brightness) {
14776#ifdef _WIN32
14777
14778 // Under Windows, we use the SetDeviceGammaRamp function which exists in
14779 // some (most modern?) versions of gdi32.dll Load the required library dll,
14780 // if not already in place
14781#ifdef ocpnUSE_GL
14782 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14783 if (g_pcurtain) {
14784 g_pcurtain->Close();
14785 g_pcurtain->Destroy();
14786 g_pcurtain = NULL;
14787 }
14788
14789 InitScreenBrightness();
14790
14791 if (NULL == hGDI32DLL) {
14792 // Unicode stuff.....
14793 wchar_t wdll_name[80];
14794 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
14795 LPCWSTR cstr = wdll_name;
14796
14797 hGDI32DLL = LoadLibrary(cstr);
14798
14799 if (NULL != hGDI32DLL) {
14800 // Get the entry points of the required functions
14801 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14802 hGDI32DLL, "SetDeviceGammaRamp");
14803 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14804 hGDI32DLL, "GetDeviceGammaRamp");
14805
14806 // If the functions are not found, unload the DLL and return false
14807 if ((NULL == g_pSetDeviceGammaRamp) ||
14808 (NULL == g_pGetDeviceGammaRamp)) {
14809 FreeLibrary(hGDI32DLL);
14810 hGDI32DLL = NULL;
14811 return 0;
14812 }
14813 }
14814 }
14815
14816 HDC hDC = GetDC(NULL); // Get the full screen DC
14817
14818 /*
14819 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
14820 if (cmcap != CM_GAMMA_RAMP)
14821 {
14822 wxLogMessage(_T(" Video hardware does not support brightness control by
14823 gamma ramp adjustment.")); return false;
14824 }
14825 */
14826
14827 int increment = brightness * 256 / 100;
14828
14829 // Build the Gamma Ramp table
14830 WORD GammaTable[3][256];
14831
14832 int table_val = 0;
14833 for (int i = 0; i < 256; i++) {
14834 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
14835 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
14836 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
14837
14838 table_val += increment;
14839
14840 if (table_val > 65535) table_val = 65535;
14841 }
14842
14843 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
14844 ReleaseDC(NULL, hDC); // Release the DC
14845
14846 return 1;
14847 }
14848#endif
14849
14850 {
14851 if (g_pSavedGammaMap) {
14852 HDC hDC = GetDC(NULL); // Get the full screen DC
14853 g_pSetDeviceGammaRamp(hDC,
14854 g_pSavedGammaMap); // Restore the saved ramp table
14855 ReleaseDC(NULL, hDC); // Release the DC
14856 }
14857
14858 if (brightness < 100) {
14859 if (NULL == g_pcurtain) InitScreenBrightness();
14860
14861 if (g_pcurtain) {
14862 int sbrite = wxMax(1, brightness);
14863 sbrite = wxMin(100, sbrite);
14864
14865 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
14866 }
14867 } else {
14868 if (g_pcurtain) {
14869 g_pcurtain->Close();
14870 g_pcurtain->Destroy();
14871 g_pcurtain = NULL;
14872 }
14873 }
14874
14875 return 1;
14876 }
14877
14878#endif
14879
14880#ifdef BRIGHT_XCALIB
14881
14882 if (!g_brightness_init) {
14883 last_brightness = 100;
14884 g_brightness_init = true;
14885 temp_file_name = wxFileName::CreateTempFileName(_T(""));
14886 InitScreenBrightness();
14887 }
14888
14889#ifdef __OPCPN_USEICC__
14890 // Create a dead simple temporary ICC profile file, with gamma ramps set as
14891 // desired, and then activate this temporary profile using xcalib <filename>
14892 if (!CreateSimpleICCProfileFile(
14893 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
14894 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
14895 wxString cmd(_T ( "xcalib " ));
14896 cmd += temp_file_name;
14897
14898 wxExecute(cmd, wxEXEC_ASYNC);
14899 }
14900
14901#else
14902 // Or, use "xcalib -co" to set overall contrast value
14903 // This is not as nice, since the -co parameter wants to be a fraction of
14904 // the current contrast, and values greater than 100 are not allowed. As a
14905 // result, increases of contrast must do a "-clear" step first, which
14906 // produces objectionable flashing.
14907 if (brightness > last_brightness) {
14908 wxString cmd;
14909 cmd = _T("xcalib -clear");
14910 wxExecute(cmd, wxEXEC_ASYNC);
14911
14912 ::wxMilliSleep(10);
14913
14914 int brite_adj = wxMax(1, brightness);
14915 cmd.Printf(_T("xcalib -co %2d -a"), brite_adj);
14916 wxExecute(cmd, wxEXEC_ASYNC);
14917 } else {
14918 int brite_adj = wxMax(1, brightness);
14919 int factor = (brite_adj * 100) / last_brightness;
14920 factor = wxMax(1, factor);
14921 wxString cmd;
14922 cmd.Printf(_T("xcalib -co %2d -a"), factor);
14923 wxExecute(cmd, wxEXEC_ASYNC);
14924 }
14925
14926#endif
14927
14928 last_brightness = brightness;
14929
14930#endif
14931
14932 return 0;
14933}
14934
14935#ifdef __OPCPN_USEICC__
14936
14937#define MLUT_TAG 0x6d4c5554L
14938#define VCGT_TAG 0x76636774L
14939
14940int GetIntEndian(unsigned char *s) {
14941 int ret;
14942 unsigned char *p;
14943 int i;
14944
14945 p = (unsigned char *)&ret;
14946
14947 if (1)
14948 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
14949 else
14950 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
14951
14952 return ret;
14953}
14954
14955unsigned short GetShortEndian(unsigned char *s) {
14956 unsigned short ret;
14957 unsigned char *p;
14958 int i;
14959
14960 p = (unsigned char *)&ret;
14961
14962 if (1)
14963 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
14964 else
14965 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
14966
14967 return ret;
14968}
14969
14970// Create a very simple Gamma correction file readable by xcalib
14971int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14972 double co_green, double co_blue) {
14973 FILE *fp;
14974
14975 if (file_name) {
14976 fp = fopen(file_name, "wb");
14977 if (!fp) return -1; /* file can not be created */
14978 } else
14979 return -1; /* filename char pointer not valid */
14980
14981 // Write header
14982 char header[128];
14983 for (int i = 0; i < 128; i++) header[i] = 0;
14984
14985 fwrite(header, 128, 1, fp);
14986
14987 // Num tags
14988 int numTags0 = 1;
14989 int numTags = GetIntEndian((unsigned char *)&numTags0);
14990 fwrite(&numTags, 1, 4, fp);
14991
14992 int tagName0 = VCGT_TAG;
14993 int tagName = GetIntEndian((unsigned char *)&tagName0);
14994 fwrite(&tagName, 1, 4, fp);
14995
14996 int tagOffset0 = 128 + 4 * sizeof(int);
14997 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
14998 fwrite(&tagOffset, 1, 4, fp);
14999
15000 int tagSize0 = 1;
15001 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15002 fwrite(&tagSize, 1, 4, fp);
15003
15004 fwrite(&tagName, 1, 4, fp); // another copy of tag
15005
15006 fwrite(&tagName, 1, 4, fp); // dummy
15007
15008 // Table type
15009
15010 /* VideoCardGammaTable (The simplest type) */
15011 int gammatype0 = 0;
15012 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15013 fwrite(&gammatype, 1, 4, fp);
15014
15015 int numChannels0 = 3;
15016 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15017 fwrite(&numChannels, 1, 2, fp);
15018
15019 int numEntries0 = 256;
15020 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15021 fwrite(&numEntries, 1, 2, fp);
15022
15023 int entrySize0 = 1;
15024 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15025 fwrite(&entrySize, 1, 2, fp);
15026
15027 unsigned char ramp[256];
15028
15029 // Red ramp
15030 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15031 fwrite(ramp, 256, 1, fp);
15032
15033 // Green ramp
15034 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15035 fwrite(ramp, 256, 1, fp);
15036
15037 // Blue ramp
15038 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15039 fwrite(ramp, 256, 1, fp);
15040
15041 fclose(fp);
15042
15043 return 0;
15044}
15045#endif // __OPCPN_USEICC__
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:4567
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4563
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:4513
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:2401
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7568
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5064
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:4669
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:4644
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5338
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:4588
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:4649
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4508
bool SetViewPoint(double lat, double lon)
Set the viewport center point.
Definition chcanv.cpp:5357
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:9888
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:68
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
Represents a navigational route in the navigation system.
Definition route.h:96
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:175
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:395
bool DeleteRoute(Route *pRoute, NavObjectChanges *nav_obj_changes)
Definition routeman.cpp:836
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