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"
51#include "model/nav_object_database.h"
52#include "model/navutil_base.h"
53#include "model/own_ship.h"
54#include "model/plugin_comm.h"
55#include "model/route.h"
56#include "model/routeman.h"
57#include "model/select.h"
58#include "model/select_item.h"
59#include "model/track.h"
60#include "model/wx28compat.h"
61
62#include "ais.h"
63#include "AISTargetAlertDialog.h"
64#include "CanvasConfig.h"
65#include "canvasMenu.h"
66#include "CanvasOptions.h"
67#include "chartdb.h"
68#include "chartimg.h"
69#include "chcanv.h"
70#include "ChInfoWin.h"
71#include "cm93.h" // for chart outline draw
72#include "compass.h"
73#include "concanv.h"
74#include "displays.h"
75#include "hotkeys_dlg.h"
76#include "FontMgr.h"
77#include "glTextureDescriptor.h"
78#include "gshhs.h"
79#include "iENCToolbar.h"
80#include "kml.h"
81#include "line_clip.h"
82#include "MarkInfo.h"
83#include "mbtiles.h"
84#include "MUIBar.h"
85#include "navutil.h"
86#include "NMEALogWindow.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#endif
127
128#ifdef __VISUALC__
129#include <wx/msw/msvcrt.h>
130#endif
131
132#ifndef __WXMSW__
133#include <signal.h>
134#include <setjmp.h>
135
136#endif
137
138extern float g_ShipScaleFactorExp;
139extern double g_mouse_zoom_sensitivity;
140
141#include <vector>
142
143#ifdef __WXMSW__
144#define printf printf2
145
146int __cdecl printf2(const char *format, ...) {
147 char str[1024];
148
149 va_list argptr;
150 va_start(argptr, format);
151 int ret = vsnprintf(str, sizeof(str), format, argptr);
152 va_end(argptr);
153 OutputDebugStringA(str);
154 return ret;
155}
156#endif
157
158#if defined(__MSVC__) && (_MSC_VER < 1700)
159#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
160#endif
161
162// Define to enable the invocation of a temporary menubar by pressing the Alt
163// key. Not implemented for Windows XP, as it interferes with Alt-Tab
164// processing.
165#define OCPN_ALT_MENUBAR 1
166
167// Profiling support
168// #include "/usr/include/valgrind/callgrind.h"
169
170// ----------------------------------------------------------------------------
171// Useful Prototypes
172// ----------------------------------------------------------------------------
173extern bool G_FloatPtInPolygon(MyFlPoint *rgpts, int wnumpts, float x, float y);
174extern void catch_signals(int signo);
175
176extern void AlphaBlending(ocpnDC &dc, int x, int y, int size_x, int size_y,
177 float radius, wxColour color,
178 unsigned char transparency);
179
180extern double g_ChartNotRenderScaleFactor;
181extern ChartDB *ChartData;
182extern bool bDBUpdateInProgress;
183extern ColorScheme global_color_scheme;
184extern int g_nbrightness;
185
186extern ConsoleCanvas *console;
187extern OCPNPlatform *g_Platform;
188
189extern RouteList *pRouteList;
190extern std::vector<Track *> g_TrackList;
191extern MyConfig *pConfig;
192extern Routeman *g_pRouteMan;
193extern ThumbWin *pthumbwin;
194extern TCMgr *ptcmgr;
195extern Select *pSelectTC;
196extern MarkInfoDlg *g_pMarkInfoDialog;
197extern RoutePropDlgImpl *pRoutePropDialog;
198extern TrackPropDlg *pTrackPropDialog;
199extern ActiveTrack *g_pActiveTrack;
200
201extern double AnchorPointMinDist;
202extern bool AnchorAlertOn1;
203extern bool AnchorAlertOn2;
204extern int g_nAWMax;
205
206extern RouteManagerDialog *pRouteManagerDialog;
207extern GoToPositionDialog *pGoToPositionDialog;
208extern wxString GetLayerName(int id);
209extern wxString g_uploadConnection;
210extern bool g_bsimplifiedScalebar;
211
212extern bool bDrawCurrentValues;
213
214extern s52plib *ps52plib;
215
216extern bool g_bTempShowMenuBar;
217extern bool g_bShowMenuBar;
218extern bool g_bShowCompassWin;
219
220extern MyFrame *gFrame;
221extern options *g_options;
222
223extern int g_iNavAidRadarRingsNumberVisible;
224extern bool g_bNavAidRadarRingsShown;
225extern float g_fNavAidRadarRingsStep;
226extern int g_pNavAidRadarRingsStepUnits;
227extern bool g_bWayPointPreventDragging;
228extern bool g_bEnableZoomToCursor;
229extern bool g_bShowChartBar;
230extern int g_ENCSoundingScaleFactor;
231extern int g_ENCTextScaleFactor;
232extern int g_maxzoomin;
233
234bool g_bShowShipToActive;
235int g_shipToActiveStyle;
236int g_shipToActiveColor;
237
238extern AISTargetQueryDialog *g_pais_query_dialog_active;
239
240extern int g_S57_dialog_sx, g_S57_dialog_sy;
241
242extern PopUpDSlide *pPopupDetailSlider;
243extern int g_detailslider_dialog_x, g_detailslider_dialog_y;
244
245extern bool g_b_overzoom_x; // Allow high overzoom
246extern double g_plus_minus_zoom_factor;
247
248extern int g_OwnShipIconType;
249extern double g_n_ownship_length_meters;
250extern double g_n_ownship_beam_meters;
251extern double g_n_gps_antenna_offset_y;
252extern double g_n_gps_antenna_offset_x;
253extern int g_n_ownship_min_mm;
254
255extern double g_COGAvg; // only needed for debug....
256
257extern int g_click_stop;
258
259extern double g_ownship_predictor_minutes;
260extern int g_cog_predictor_style;
261extern wxString g_cog_predictor_color;
262extern int g_cog_predictor_endmarker;
263extern int g_ownship_HDTpredictor_style;
264extern wxString g_ownship_HDTpredictor_color;
265extern int g_ownship_HDTpredictor_endmarker;
266extern int g_ownship_HDTpredictor_width;
267extern double g_ownship_HDTpredictor_miles;
268
269extern bool g_bquiting;
270extern AISTargetListDialog *g_pAISTargetList;
271
272extern PlugInManager *g_pi_manager;
273
274extern OCPN_AUIManager *g_pauimgr;
275
276extern bool g_bopengl;
277
278extern bool g_bFullScreenQuilt;
279
280extern bool g_bsmoothpanzoom;
281extern bool g_bSmoothRecenter;
282
283bool g_bDebugOGL;
284
285extern bool g_b_assume_azerty;
286
287extern ChartGroupArray *g_pGroupArray;
288
289extern S57QueryDialog *g_pObjectQueryDialog;
290extern ocpnStyle::StyleManager *g_StyleManager;
291
292extern OcpnSound *g_anchorwatch_sound;
293
294extern bool g_bresponsive;
295extern int g_chart_zoom_modifier_raster;
296extern int g_chart_zoom_modifier_vector;
297extern int g_ChartScaleFactor;
298
299#ifdef ocpnUSE_GL
300#endif
301
302extern double g_gl_ms_per_frame;
303extern bool g_benable_rotate;
304extern bool g_bRollover;
305
306extern bool g_bSpaceDropMark;
307extern bool g_bAutoHideToolbar;
308extern int g_nAutoHideToolbar;
309extern bool g_bDeferredInitDone;
310
311extern wxString g_CmdSoundString;
312ShapeBaseChartSet gShapeBasemap;
313
314// TODO why are these static?
316static int mouse_x;
318static int mouse_y;
319static bool mouse_leftisdown;
320
321bool g_brouteCreating;
322
323bool g_bShowTrackPointTime;
324
325int r_gamma_mult;
326int g_gamma_mult;
327int b_gamma_mult;
328int gamma_state;
329bool g_brightness_init;
330int last_brightness;
331
332int g_cog_predictor_width;
333extern double g_display_size_mm;
334
335extern ocpnFloatingToolbarDialog *g_MainToolbar;
336extern iENCToolbar *g_iENCToolbar;
337extern wxColour g_colourOwnshipRangeRingsColour;
338
339// LIVE ETA OPTION
340bool g_bShowLiveETA;
341extern double g_defaultBoatSpeed;
342double g_defaultBoatSpeedUserUnit;
343
344extern int g_nAIS_activity_timer;
345extern bool g_bskew_comp;
346extern float g_compass_scalefactor;
347extern int g_COGAvgSec; // COG average period (sec.) for Course Up Mode
348extern bool g_btenhertz;
349
350wxGLContext *g_pGLcontext; // shared common context
351
352extern bool g_useMUI;
353extern unsigned int g_canvasConfig;
354
355extern ChartCanvas *g_focusCanvas;
356extern ChartCanvas *g_overlayCanvas;
357
358extern float g_toolbar_scalefactor;
359extern SENCThreadManager *g_SencThreadManager;
360
361wxString g_ObjQFileExt;
362
363// "Curtain" mode parameters
364wxDialog *g_pcurtain;
365
366extern int g_GUIScaleFactor;
367// Win DPI scale factor
368double g_scaler;
369wxString g_lastS52PLIBPluginMessage;
370extern bool g_bChartBarEx;
371bool g_PrintingInProgress;
372
373#define MIN_BRIGHT 10
374#define MAX_BRIGHT 100
375
376//------------------------------------------------------------------------------
377// ChartCanvas Implementation
378//------------------------------------------------------------------------------
379BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
380EVT_PAINT(ChartCanvas::OnPaint)
381EVT_ACTIVATE(ChartCanvas::OnActivate)
382EVT_SIZE(ChartCanvas::OnSize)
383#ifndef HAVE_WX_GESTURE_EVENTS
384EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
385#endif
386EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
387EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
388EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
389EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
390EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
391EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
392EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
393EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
394EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
395EVT_KEY_UP(ChartCanvas::OnKeyUp)
396EVT_CHAR(ChartCanvas::OnKeyChar)
397EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
398EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
399EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
400EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
401EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
402EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
403EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
404EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
405
406END_EVENT_TABLE()
407
408// Define a constructor for my canvas
409ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex)
410 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER) {
411 parent_frame = (MyFrame *)frame; // save a pointer to parent
412 m_canvasIndex = canvasIndex;
413
414 pscratch_bm = NULL;
415
416 SetBackgroundColour(wxColour(0, 0, 0));
417 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
418 // color scheme change
419
420 m_groupIndex = 0;
421 m_bDrawingRoute = false;
422 m_bRouteEditing = false;
423 m_bMarkEditing = false;
424 m_bRoutePoinDragging = false;
425 m_bIsInRadius = false;
426 m_bMayToggleMenuBar = true;
427
428 m_bFollow = false;
429 m_bShowNavobjects = true;
430 m_bTCupdate = false;
431 m_bAppendingRoute = false; // was true in MSW, why??
432 pThumbDIBShow = NULL;
433 m_bShowCurrent = false;
434 m_bShowTide = false;
435 bShowingCurrent = false;
436 pCwin = NULL;
437 warp_flag = false;
438 m_bzooming = false;
439 m_b_paint_enable = true;
440 m_routeState = 0;
441
442 pss_overlay_bmp = NULL;
443 pss_overlay_mask = NULL;
444 m_bChartDragging = false;
445 m_bMeasure_Active = false;
446 m_bMeasure_DistCircle = false;
447 m_pMeasureRoute = NULL;
448 m_pTrackRolloverWin = NULL;
449 m_pRouteRolloverWin = NULL;
450 m_pAISRolloverWin = NULL;
451 m_bedge_pan = false;
452 m_disable_edge_pan = false;
453 m_dragoffsetSet = false;
454 m_bautofind = false;
455 m_bFirstAuto = true;
456 m_groupIndex = 0;
457 m_singleChart = NULL;
458 m_upMode = NORTH_UP_MODE;
459 m_bShowAIS = true;
460 m_bShowAISScaled = false;
461 m_timed_move_vp_active = false;
462
463 m_vLat = 0.;
464 m_vLon = 0.;
465
466 m_pCIWin = NULL;
467
468 m_pSelectedRoute = NULL;
469 m_pSelectedTrack = NULL;
470 m_pRoutePointEditTarget = NULL;
471 m_pFoundPoint = NULL;
472 m_pMouseRoute = NULL;
473 m_prev_pMousePoint = NULL;
474 m_pEditRouteArray = NULL;
475 m_pFoundRoutePoint = NULL;
476 m_FinishRouteOnKillFocus = true;
477
478 m_pRolloverRouteSeg = NULL;
479 m_pRolloverTrackSeg = NULL;
480 m_bsectors_shown = false;
481
482 m_bbrightdir = false;
483 r_gamma_mult = 1;
484 g_gamma_mult = 1;
485 b_gamma_mult = 1;
486
487 m_pos_image_user_day = NULL;
488 m_pos_image_user_dusk = NULL;
489 m_pos_image_user_night = NULL;
490 m_pos_image_user_grey_day = NULL;
491 m_pos_image_user_grey_dusk = NULL;
492 m_pos_image_user_grey_night = NULL;
493
494 m_zoom_factor = 1;
495 m_rotation_speed = 0;
496 m_mustmove = 0;
497
498 m_OSoffsetx = 0.;
499 m_OSoffsety = 0.;
500
501 m_pos_image_user_yellow_day = NULL;
502 m_pos_image_user_yellow_dusk = NULL;
503 m_pos_image_user_yellow_night = NULL;
504
505 SetOwnShipState(SHIP_INVALID);
506
507 undo = new Undo(this);
508
509 VPoint.Invalidate();
510
511 m_glcc = NULL;
512
513 m_focus_indicator_pix = 1;
514
515 m_pCurrentStack = NULL;
516 m_bpersistent_quilt = false;
517 m_piano_ctx_menu = NULL;
518 m_Compass = NULL;
519
520 g_ChartNotRenderScaleFactor = 2.0;
521 m_bShowScaleInStatusBar = true;
522
523 m_muiBar = NULL;
524 m_bShowScaleInStatusBar = false;
525 m_show_focus_bar = true;
526
527 m_bShowOutlines = false;
528 m_bDisplayGrid = false;
529 m_bShowDepthUnits = true;
530 m_encDisplayCategory = (int)STANDARD;
531
532 m_encShowLights = true;
533 m_encShowAnchor = true;
534 m_encShowDataQual = false;
535 m_bShowGPS = true;
536 m_pQuilt = new Quilt(this);
537 SetQuiltMode(true);
538 SetAlertString(_T(""));
539 m_sector_glat = 0;
540 m_sector_glon = 0;
541 g_PrintingInProgress = false;
542
543#ifdef HAVE_WX_GESTURE_EVENTS
544 m_oldVPSScale = -1.0;
545 m_popupWanted = false;
546 m_leftdown = false;
547#endif /* HAVE_WX_GESTURE_EVENTS */
548
549 SetupGlCanvas();
550
551 singleClickEventIsValid = false;
552
553 // Build the cursors
554
555 pCursorLeft = NULL;
556 pCursorRight = NULL;
557 pCursorUp = NULL;
558 pCursorDown = NULL;
559 pCursorArrow = NULL;
560 pCursorPencil = NULL;
561 pCursorCross = NULL;
562
563 RebuildCursors();
564
565 SetCursor(*pCursorArrow);
566
567 pPanTimer = new wxTimer(this, m_MouseDragging);
568 pPanTimer->Stop();
569
570 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
571 pMovementTimer->Stop();
572
573 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
574 pMovementStopTimer->Stop();
575
576 pRotDefTimer = new wxTimer(this, ROT_TIMER);
577 pRotDefTimer->Stop();
578
579 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
580 m_DoubleClickTimer->Stop();
581
582 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
583 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
584 m_chart_drag_inertia_active = false;
585
586 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
587 m_animationActive = false;
588
589 m_panx = m_pany = 0;
590 m_panspeed = 0;
591 m_panx_target_final = m_pany_target_final = 0;
592 m_panx_target_now = m_pany_target_now = 0;
593
594 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
595 pCurTrackTimer->Stop();
596 m_curtrack_timer_msec = 10;
597
598 m_wheelzoom_stop_oneshot = 0;
599 m_last_wheel_dir = 0;
600
601 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
602
603 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
604
605 m_rollover_popup_timer_msec = 20;
606
607 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
608
609 m_b_rot_hidef = true;
610
611 proute_bm = NULL;
612 m_prot_bm = NULL;
613
614 m_upMode = NORTH_UP_MODE;
615 m_bLookAhead = false;
616
617 // Set some benign initial values
618
619 m_cs = GLOBAL_COLOR_SCHEME_DAY;
620 VPoint.clat = 0;
621 VPoint.clon = 0;
622 VPoint.view_scale_ppm = 1;
623 VPoint.Invalidate();
624 m_nMeasureState = 0;
625
626 m_canvas_scale_factor = 1.;
627
628 m_canvas_width = 1000;
629
630 m_overzoomTextWidth = 0;
631 m_overzoomTextHeight = 0;
632
633 // Create the default world chart
634 pWorldBackgroundChart = new GSHHSChart;
635 gShapeBasemap.Reset();
636
637 // Create the default depth unit emboss maps
638 m_pEM_Feet = NULL;
639 m_pEM_Meters = NULL;
640 m_pEM_Fathoms = NULL;
641
642 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
643
644 m_pEM_OverZoom = NULL;
645 SetOverzoomFont();
646 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
647
648 // Build icons for tide/current points
649 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
650 m_bmTideDay = style->GetIconScaled(_T("tidesml"),
651 1. / g_Platform->GetDisplayDIPMult(this));
652
653 // Dusk
654 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
655
656 // Night
657 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
658
659 // Build Dusk/Night ownship icons
660 double factor_dusk = 0.5;
661 double factor_night = 0.25;
662
663 // Red
664 m_os_image_red_day = style->GetIcon(_T("ship-red")).ConvertToImage();
665
666 int rimg_width = m_os_image_red_day.GetWidth();
667 int rimg_height = m_os_image_red_day.GetHeight();
668
669 m_os_image_red_dusk = m_os_image_red_day.Copy();
670 m_os_image_red_night = m_os_image_red_day.Copy();
671
672 for (int iy = 0; iy < rimg_height; iy++) {
673 for (int ix = 0; ix < rimg_width; ix++) {
674 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
675 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
676 m_os_image_red_day.GetGreen(ix, iy),
677 m_os_image_red_day.GetBlue(ix, iy));
678 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
679 hsv.value = hsv.value * factor_dusk;
680 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
681 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
682
683 hsv = wxImage::RGBtoHSV(rgb);
684 hsv.value = hsv.value * factor_night;
685 nrgb = wxImage::HSVtoRGB(hsv);
686 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
687 }
688 }
689 }
690
691 // Grey
692 m_os_image_grey_day =
693 style->GetIcon(_T("ship-red")).ConvertToImage().ConvertToGreyscale();
694
695 int gimg_width = m_os_image_grey_day.GetWidth();
696 int gimg_height = m_os_image_grey_day.GetHeight();
697
698 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
699 m_os_image_grey_night = m_os_image_grey_day.Copy();
700
701 for (int iy = 0; iy < gimg_height; iy++) {
702 for (int ix = 0; ix < gimg_width; ix++) {
703 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
704 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
705 m_os_image_grey_day.GetGreen(ix, iy),
706 m_os_image_grey_day.GetBlue(ix, iy));
707 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
708 hsv.value = hsv.value * factor_dusk;
709 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
710 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
711
712 hsv = wxImage::RGBtoHSV(rgb);
713 hsv.value = hsv.value * factor_night;
714 nrgb = wxImage::HSVtoRGB(hsv);
715 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
716 }
717 }
718 }
719
720 // Yellow
721 m_os_image_yellow_day = m_os_image_red_day.Copy();
722
723 gimg_width = m_os_image_yellow_day.GetWidth();
724 gimg_height = m_os_image_yellow_day.GetHeight();
725
726 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
727 m_os_image_yellow_night = m_os_image_red_day.Copy();
728
729 for (int iy = 0; iy < gimg_height; iy++) {
730 for (int ix = 0; ix < gimg_width; ix++) {
731 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
732 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
733 m_os_image_yellow_day.GetGreen(ix, iy),
734 m_os_image_yellow_day.GetBlue(ix, iy));
735 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
736 hsv.hue += 60. / 360.; // shift to yellow
737 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
738 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
739
740 hsv = wxImage::RGBtoHSV(rgb);
741 hsv.value = hsv.value * factor_dusk;
742 hsv.hue += 60. / 360.; // shift to yellow
743 nrgb = wxImage::HSVtoRGB(hsv);
744 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
745
746 hsv = wxImage::RGBtoHSV(rgb);
747 hsv.hue += 60. / 360.; // shift to yellow
748 hsv.value = hsv.value * factor_night;
749 nrgb = wxImage::HSVtoRGB(hsv);
750 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
751 }
752 }
753 }
754
755 // Set initial pointers to ownship images
756 m_pos_image_red = &m_os_image_red_day;
757 m_pos_image_yellow = &m_os_image_yellow_day;
758 m_pos_image_grey = &m_os_image_grey_day;
759
760 SetUserOwnship();
761
762 m_pBrightPopup = NULL;
763
764#ifdef ocpnUSE_GL
765 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
766#endif
767
768 int gridFontSize = 8;
769#if defined(__WXOSX__) || defined(__WXGTK3__)
770 // Support scaled HDPI displays.
771 gridFontSize *= GetContentScaleFactor();
772#endif
773
774 m_pgridFont = FontMgr::Get().FindOrCreateFont(
775 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
776 FALSE, wxString(_T ( "Arial" )));
777
778 m_Piano = new Piano(this);
779
780 m_bShowCompassWin = true;
781
782 m_Compass = new ocpnCompass(this);
783 m_Compass->SetScaleFactor(g_compass_scalefactor);
784 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
785
786 m_pianoFrozen = false;
787
788 SetMinSize(wxSize(200, 200));
789
790 m_displayScale = 1.0;
791#if defined(__WXOSX__) || defined(__WXGTK3__)
792 // Support scaled HDPI displays.
793 m_displayScale = GetContentScaleFactor();
794#endif
795 VPoint.SetPixelScale(m_displayScale);
796
797#ifdef HAVE_WX_GESTURE_EVENTS
798 // if (!m_glcc)
799 {
800 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
801 wxLogError("Failed to enable touch events");
802 }
803
804 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
805
806 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
807 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
808
809 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
810 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
811
812 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
813 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
814
815 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
816 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
817 }
818#endif
819}
820
821ChartCanvas::~ChartCanvas() {
822 delete pThumbDIBShow;
823
824 // Delete Cursors
825 delete pCursorLeft;
826 delete pCursorRight;
827 delete pCursorUp;
828 delete pCursorDown;
829 delete pCursorArrow;
830 delete pCursorPencil;
831 delete pCursorCross;
832
833 delete pPanTimer;
834 delete pMovementTimer;
835 delete pMovementStopTimer;
836 delete pCurTrackTimer;
837 delete pRotDefTimer;
838 delete m_DoubleClickTimer;
839
840 delete m_pTrackRolloverWin;
841 delete m_pRouteRolloverWin;
842 delete m_pAISRolloverWin;
843 delete m_pBrightPopup;
844
845 delete m_pCIWin;
846
847 delete pscratch_bm;
848
849 m_dc_route.SelectObject(wxNullBitmap);
850 delete proute_bm;
851
852 delete pWorldBackgroundChart;
853 delete pss_overlay_bmp;
854
855 delete m_pEM_Feet;
856 delete m_pEM_Meters;
857 delete m_pEM_Fathoms;
858
859 delete m_pEM_OverZoom;
860 // delete m_pEM_CM93Offset;
861
862 delete m_prot_bm;
863
864 delete m_pos_image_user_day;
865 delete m_pos_image_user_dusk;
866 delete m_pos_image_user_night;
867 delete m_pos_image_user_grey_day;
868 delete m_pos_image_user_grey_dusk;
869 delete m_pos_image_user_grey_night;
870 delete m_pos_image_user_yellow_day;
871 delete m_pos_image_user_yellow_dusk;
872 delete m_pos_image_user_yellow_night;
873
874 delete undo;
875#ifdef ocpnUSE_GL
876 if (!g_bdisable_opengl) {
877 delete m_glcc;
878
879#if wxCHECK_VERSION(2, 9, 0)
880 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
881#endif
882 }
883#endif
884
885 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
886 // wx tries to deliver events to this canvas during destroy.
887 MUIBar *muiBar = m_muiBar;
888 m_muiBar = 0;
889 delete muiBar;
890 delete m_pQuilt;
891 delete m_pCurrentStack;
892 delete m_Compass;
893 delete m_Piano;
894}
895
896void ChartCanvas::RebuildCursors() {
897 delete pCursorLeft;
898 delete pCursorRight;
899 delete pCursorUp;
900 delete pCursorDown;
901 delete pCursorArrow;
902 delete pCursorPencil;
903 delete pCursorCross;
904
905 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
906 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
907
908 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
909
910 wxImage ICursorLeft = style->GetIcon(_T("left")).ConvertToImage();
911 wxImage ICursorRight = style->GetIcon(_T("right")).ConvertToImage();
912 wxImage ICursorUp = style->GetIcon(_T("up")).ConvertToImage();
913 wxImage ICursorDown = style->GetIcon(_T("down")).ConvertToImage();
914 wxImage ICursorPencil =
915 style->GetIconScaled(_T("pencil"), pencilScale).ConvertToImage();
916 wxImage ICursorCross = style->GetIcon(_T("cross")).ConvertToImage();
917
918#if !defined(__WXMSW__) && !defined(__WXQT__)
919 ICursorLeft.ConvertAlphaToMask(128);
920 ICursorRight.ConvertAlphaToMask(128);
921 ICursorUp.ConvertAlphaToMask(128);
922 ICursorDown.ConvertAlphaToMask(128);
923 ICursorPencil.ConvertAlphaToMask(10);
924 ICursorCross.ConvertAlphaToMask(10);
925#endif
926
927 if (ICursorLeft.Ok()) {
928 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
929 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
930 pCursorLeft = new wxCursor(ICursorLeft);
931 } else
932 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
933
934 if (ICursorRight.Ok()) {
935 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
936 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
937 pCursorRight = new wxCursor(ICursorRight);
938 } else
939 pCursorRight = new wxCursor(wxCURSOR_ARROW);
940
941 if (ICursorUp.Ok()) {
942 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
943 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
944 pCursorUp = new wxCursor(ICursorUp);
945 } else
946 pCursorUp = new wxCursor(wxCURSOR_ARROW);
947
948 if (ICursorDown.Ok()) {
949 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
950 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
951 pCursorDown = new wxCursor(ICursorDown);
952 } else
953 pCursorDown = new wxCursor(wxCURSOR_ARROW);
954
955 if (ICursorPencil.Ok()) {
956 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
957 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
958 pCursorPencil = new wxCursor(ICursorPencil);
959 } else
960 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
961
962 if (ICursorCross.Ok()) {
963 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
964 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
965 pCursorCross = new wxCursor(ICursorCross);
966 } else
967 pCursorCross = new wxCursor(wxCURSOR_ARROW);
968
969 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
970 pPlugIn_Cursor = NULL;
971}
972
973void ChartCanvas::CanvasApplyLocale() {
974 CreateDepthUnitEmbossMaps(m_cs);
975 CreateOZEmbossMapData(m_cs);
976}
977
978void ChartCanvas::SetupGlCanvas() {
979#ifndef __ANDROID__
980#ifdef ocpnUSE_GL
981 if (!g_bdisable_opengl) {
982 if (g_bopengl) {
983 wxLogMessage(_T("Creating glChartCanvas"));
984 m_glcc = new glChartCanvas(this);
985
986 // We use one context for all GL windows, so that textures etc will be
987 // automatically shared
988 if (IsPrimaryCanvas()) {
989 // qDebug() << "Creating Primary Context";
990
991 // wxGLContextAttrs ctxAttr;
992 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
993 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
994 // NULL, &ctxAttr);
995 wxGLContext *pctx = new wxGLContext(m_glcc);
996 m_glcc->SetContext(pctx);
997 g_pGLcontext = pctx; // Save a copy of the common context
998 } else {
999#ifdef __WXOSX__
1000 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
1001#else
1002 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
1003 // saved common context
1004#endif
1005 }
1006 }
1007 }
1008#endif
1009#endif
1010
1011#ifdef __ANDROID__ // ocpnUSE_GL
1012 if (!g_bdisable_opengl) {
1013 if (g_bopengl) {
1014 // qDebug() << "SetupGlCanvas";
1015 wxLogMessage(_T("Creating glChartCanvas"));
1016
1017 // We use one context for all GL windows, so that textures etc will be
1018 // automatically shared
1019 if (IsPrimaryCanvas()) {
1020 qDebug() << "Creating Primary glChartCanvas";
1021
1022 // wxGLContextAttrs ctxAttr;
1023 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
1024 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
1025 // NULL, &ctxAttr);
1026 m_glcc = new glChartCanvas(this);
1027
1028 wxGLContext *pctx = new wxGLContext(m_glcc);
1029 m_glcc->SetContext(pctx);
1030 g_pGLcontext = pctx; // Save a copy of the common context
1031 m_glcc->m_pParentCanvas = this;
1032 // m_glcc->Reparent(this);
1033 } else {
1034 qDebug() << "Creating Secondary glChartCanvas";
1035 // QGLContext *pctx =
1036 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
1037 // << "pctx: " << pctx;
1038
1039 m_glcc = new glChartCanvas(
1040 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
1041 // m_glcc = new glChartCanvas(this, pctx); //Shared
1042 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
1043 wxGLContext *pwxctx = new wxGLContext(m_glcc);
1044 m_glcc->SetContext(pwxctx);
1045 m_glcc->m_pParentCanvas = this;
1046 // m_glcc->Reparent(this);
1047 }
1048 }
1049 }
1050#endif
1051}
1052
1053void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
1054 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1055
1056 // On Android, we get a KillFocus on just about every keystroke.
1057 // Why?
1058#ifdef __ANDROID__
1059 return;
1060#endif
1061
1062 // Special logic:
1063 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
1064 // canvas focus. Why??? Who knows... So, we provide for this case by
1065 // starting a timer if required to actually Finish() a route on a legitimate
1066 // focus change, but not if the focus is quickly regained ( <20 msec.) on
1067 // this canvas.
1068#ifdef __WXOSX__
1069 if (m_routeState && m_FinishRouteOnKillFocus)
1070 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
1071#else
1072 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1073#endif
1074}
1075
1076void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
1077 m_routeFinishTimer.Stop();
1078
1079 // Try to keep the global top-line menubar selections up to date with the
1080 // current "focus" canvas
1081 gFrame->UpdateGlobalMenuItems(this);
1082
1083 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
1084}
1085
1086void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
1087 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
1088}
1089
1090#ifdef HAVE_WX_GESTURE_EVENTS
1091void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
1092 /* we defer the popup menu call upon the leftup event
1093 else the menu disappears immediately,
1094 (see
1095 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
1096 */
1097 m_popupWanted = true;
1098}
1099
1100void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
1101 // not implemented yet
1102}
1103
1104void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1105
1106void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1107
1108void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1109 wxPoint pos = event.GetPosition();
1110
1111 m_leftdown = false;
1112
1113 if (!m_popupWanted) {
1114 wxMouseEvent ev(wxEVT_LEFT_UP);
1115 ev.m_x = pos.x;
1116 ev.m_y = pos.y;
1117 MouseEvent(ev);
1118 return;
1119 }
1120
1121 m_popupWanted = false;
1122
1123 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1124 ev.m_x = pos.x;
1125 ev.m_y = pos.y;
1126
1127 MouseEvent(ev);
1128}
1129
1130void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1131 m_leftdown = true;
1132
1133 wxPoint pos = event.GetPosition();
1134 MouseEvent(event);
1135}
1136
1137void ChartCanvas::OnMotion(wxMouseEvent &event) {
1138 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1139 dragging, upon simple click, and without the OnLeftDown event before Thus,
1140 this consists in skiping it, and setting the leftdown bit according to a
1141 status that we trust */
1142 event.m_leftDown = m_leftdown;
1143 MouseEvent(event);
1144}
1145
1146void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1147 /* there are spurious end zoom events upon right-click */
1148 if (event.IsGestureEnd()) return;
1149
1150 double factor = event.GetZoomFactor();
1151
1152 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1153 m_oldVPSScale = GetVPScale();
1154 }
1155
1156 double current_vps = GetVPScale();
1157 double wanted_factor = m_oldVPSScale / current_vps * factor;
1158
1159 ZoomCanvas(wanted_factor, true, false);
1160
1161 // Allow combined zoom/pan operation
1162 if (event.IsGestureStart()) {
1163 m_zoomStartPoint = event.GetPosition();
1164 } else {
1165 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1166 PanCanvas(-delta.x, -delta.y);
1167 m_zoomStartPoint = event.GetPosition();
1168 }
1169}
1170
1171void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1172
1173void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1174 DoRotateCanvas(0.0);
1175}
1176#endif /* HAVE_WX_GESTURE_EVENTS */
1177
1178void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1179 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1180 m_vLat = pcc->iLat;
1181 m_vLon = pcc->iLon;
1182
1183 m_restore_dbindex = pcc->DBindex;
1184 m_bFollow = pcc->bFollow;
1185 if (pcc->GroupID < 0) pcc->GroupID = 0;
1186
1187 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1188 m_groupIndex = 0;
1189 else
1190 m_groupIndex = pcc->GroupID;
1191
1192 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1193
1194 ShowTides(pcc->bShowTides);
1195 ShowCurrents(pcc->bShowCurrents);
1196
1197 SetShowDepthUnits(pcc->bShowDepthUnits);
1198 SetShowGrid(pcc->bShowGrid);
1199 SetShowOutlines(pcc->bShowOutlines);
1200
1201 SetShowAIS(pcc->bShowAIS);
1202 SetAttenAIS(pcc->bAttenAIS);
1203
1204 // ENC options
1205 SetShowENCText(pcc->bShowENCText);
1206 m_encDisplayCategory = pcc->nENCDisplayCategory;
1207 m_encShowDepth = pcc->bShowENCDepths;
1208 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1209 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1210 m_encShowLights = pcc->bShowENCLights;
1211 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1212 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1213 m_encShowDataQual = pcc->bShowENCDataQuality;
1214
1215 bool courseUp = pcc->bCourseUp;
1216 bool headUp = pcc->bHeadUp;
1217 m_upMode = NORTH_UP_MODE;
1218 if (courseUp)
1219 m_upMode = COURSE_UP_MODE;
1220 else if (headUp)
1221 m_upMode = HEAD_UP_MODE;
1222
1223 m_bLookAhead = pcc->bLookahead;
1224
1225 m_singleChart = NULL;
1226}
1227
1228void ChartCanvas::ApplyGlobalSettings() {
1229 // GPS compas window
1230 if (m_Compass) {
1231 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1232 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1233 }
1234}
1235
1236void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1237 bool groupOK = CheckGroup(m_groupIndex);
1238
1239 if (!groupOK) {
1240 SetGroupIndex(m_groupIndex, true);
1241 }
1242}
1243
1244void ChartCanvas::SetShowGPS(bool bshow) {
1245 if (m_bShowGPS != bshow) {
1246 delete m_Compass;
1247 m_Compass = new ocpnCompass(this, bshow);
1248 m_Compass->SetScaleFactor(g_compass_scalefactor);
1249 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1250 }
1251 m_bShowGPS = bshow;
1252}
1253
1254void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1255 m_bShowCompassWin = bshow;
1256 if (m_Compass) {
1257 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1258 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1259 }
1260}
1261
1262int ChartCanvas::GetPianoHeight() {
1263 int height = 0;
1264 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1265
1266 return height;
1267}
1268
1269void ChartCanvas::ConfigureChartBar() {
1270 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1271
1272 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
1273 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
1274
1275 if (GetQuiltMode()) {
1276 m_Piano->SetRoundedRectangles(true);
1277 }
1278 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
1279 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon(_T("polyprj"))));
1280 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
1281}
1282
1283void ChartCanvas::ShowTides(bool bShow) {
1284 gFrame->LoadHarmonics();
1285
1286 if (ptcmgr->IsReady()) {
1287 SetbShowTide(bShow);
1288
1289 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1290 } else {
1291 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1292 SetbShowTide(false);
1293 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1294 }
1295
1296 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1297 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1298
1299 // TODO
1300 // if( GetbShowTide() ) {
1301 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1302 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1303 // update
1304 // } else
1305 // FrameTCTimer.Stop();
1306}
1307
1308void ChartCanvas::ShowCurrents(bool bShow) {
1309 gFrame->LoadHarmonics();
1310
1311 if (ptcmgr->IsReady()) {
1312 SetbShowCurrent(bShow);
1313 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1314 } else {
1315 wxLogMessage(_T("Chart1::Event...TCMgr Not Available"));
1316 SetbShowCurrent(false);
1317 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1318 }
1319
1320 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1321 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1322
1323 // TODO
1324 // if( GetbShowCurrent() ) {
1325 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1326 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1327 // update
1328 // } else
1329 // FrameTCTimer.Stop();
1330}
1331
1332// TODO
1333extern bool g_bPreserveScaleOnX;
1334extern ChartDummy *pDummyChart;
1335extern int g_sticky_chart;
1336
1337void ChartCanvas::canvasRefreshGroupIndex(void) { SetGroupIndex(m_groupIndex); }
1338
1339void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1340 SetAlertString(_T(""));
1341
1342 int new_index = index;
1343 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1344
1345 bool bgroup_override = false;
1346 int old_group_index = new_index;
1347
1348 if (!CheckGroup(new_index)) {
1349 new_index = 0;
1350 bgroup_override = true;
1351 }
1352
1353 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1354 new_index = index;
1355
1356 // Get the currently displayed chart native scale, and the current ViewPort
1357 int current_chart_native_scale = GetCanvasChartNativeScale();
1358 ViewPort vp = GetVP();
1359
1360 m_groupIndex = new_index;
1361
1362 // Are there ENCs in this group
1363 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1364
1365 // Update the MUIBar for ENC availability
1366 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1367
1368 // Allow the chart database to pre-calculate the MBTile inclusion test
1369 // boolean...
1370 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1371
1372 // Invalidate the "sticky" chart on group change, since it might not be in
1373 // the new group
1374 g_sticky_chart = -1;
1375
1376 // We need a chartstack and quilt to figure out which chart to open in the
1377 // new group
1378 UpdateCanvasOnGroupChange();
1379
1380 int dbi_now = -1;
1381 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1382
1383 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1384
1385 // If a new reference chart is indicated, set a good scale for it.
1386 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1387 double best_scale = GetBestStartScale(dbi_hint, vp);
1388 SetVPScale(best_scale);
1389 }
1390
1391 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1392
1393 // Refresh the canvas, selecting the "best" chart,
1394 // applying the prior ViewPort exactly
1395 canvasChartsRefresh(dbi_hint);
1396
1397 UpdateCanvasControlBar();
1398
1399 if (!autoSwitch && bgroup_override) {
1400 // show a short timed message box
1401 wxString msg(_("Group \""));
1402
1403 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1404 msg += pGroup->m_group_name;
1405
1406 msg += _("\" is empty.");
1407
1408 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1409
1410 return;
1411 }
1412
1413 // Message box is deferred so that canvas refresh occurs properly before
1414 // dialog
1415 if (bgroup_override) {
1416 wxString msg(_("Group \""));
1417
1418 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1419 msg += pGroup->m_group_name;
1420
1421 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1422
1423 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1424 }
1425}
1426
1427bool ChartCanvas::CheckGroup(int igroup) {
1428 if (!ChartData) return true; // Not known yet...
1429
1430 if (igroup == 0) return true; // "all charts" is always OK
1431
1432 if (igroup < 0) // negative group is an error
1433 return false;
1434
1435 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1436
1437 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1438 // and auto-shift to group 0
1439 return false;
1440
1441 for (const auto &elem : pGroup->m_element_array) {
1442 for (unsigned int ic = 0;
1443 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1444 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1445 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1446
1447 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1448 }
1449 }
1450
1451 // If necessary, check for GSHHS
1452 for (const auto &elem : pGroup->m_element_array) {
1453 const wxString &element_root = elem.m_element_name;
1454 wxString test_string = _T("GSHH");
1455 if (element_root.Upper().Contains(test_string)) return true;
1456 }
1457
1458 return false;
1459}
1460
1461void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1462 if (!ChartData) return;
1463
1464 AbstractPlatform::ShowBusySpinner();
1465
1466 double old_scale = GetVPScale();
1467 InvalidateQuilt();
1468 SetQuiltRefChart(-1);
1469
1470 m_singleChart = NULL;
1471
1472 // delete m_pCurrentStack;
1473 // m_pCurrentStack = NULL;
1474
1475 // Build a new ChartStack
1476 if (!m_pCurrentStack) {
1477 m_pCurrentStack = new ChartStack;
1478 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1479 }
1480
1481 if (-1 != dbi_hint) {
1482 if (GetQuiltMode()) {
1483 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1484 SetQuiltRefChart(dbi_hint);
1485 } else {
1486 // Open the saved chart
1487 ChartBase *pTentative_Chart;
1488 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1489
1490 if (pTentative_Chart) {
1491 /* m_singleChart is always NULL here, (set above) should this go before
1492 * that? */
1493 if (m_singleChart) m_singleChart->Deactivate();
1494
1495 m_singleChart = pTentative_Chart;
1496 m_singleChart->Activate();
1497
1498 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1499 GetpCurrentStack(), m_singleChart->GetFullPath());
1500 }
1501 }
1502
1503 // refresh_Piano();
1504 } else {
1505 // Select reference chart from the stack, as though clicked by user
1506 // Make it the smallest scale chart on the stack
1507 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1508 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1509 SetQuiltRefChart(selected_index);
1510 }
1511
1512 // Validate the correct single chart, or set the quilt mode as appropriate
1513 SetupCanvasQuiltMode();
1514 if (!GetQuiltMode() && m_singleChart == 0) {
1515 // use a dummy like in DoChartUpdate
1516 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1517 m_singleChart = pDummyChart;
1518 SetVPScale(old_scale);
1519 }
1520
1521 ReloadVP();
1522
1523 UpdateCanvasControlBar();
1524 UpdateGPSCompassStatusBox(true);
1525
1526 SetCursor(wxCURSOR_ARROW);
1527
1528 AbstractPlatform::HideBusySpinner();
1529}
1530
1531bool ChartCanvas::DoCanvasUpdate(void) {
1532 double tLat, tLon; // Chart Stack location
1533 double vpLat, vpLon; // ViewPort location
1534 bool blong_jump = false;
1535 meters_to_shift = 0;
1536 dir_to_shift = 0;
1537
1538 bool bNewChart = false;
1539 bool bNewView = false;
1540 bool bCanvasChartAutoOpen = true; // debugging
1541
1542 bool bNewPiano = false;
1543 bool bOpenSpecified;
1544 ChartStack LastStack;
1545 ChartBase *pLast_Ch;
1546
1547 ChartStack WorkStack;
1548
1549 if (bDBUpdateInProgress) return false;
1550 if (!ChartData) return false;
1551
1552 if (ChartData->IsBusy()) return false;
1553
1554 // Startup case:
1555 // Quilting is enabled, but the last chart seen was not quiltable
1556 // In this case, drop to single chart mode, set persistence flag,
1557 // And open the specified chart
1558 // TODO implement this
1559 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1560 // if( GetQuiltMode() ) {
1561 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1562 // gFrame->ToggleQuiltMode();
1563 // m_bpersistent_quilt = true;
1564 // m_singleChart = NULL;
1565 // }
1566 // }
1567 // }
1568
1569 // If in auto-follow mode, use the current glat,glon to build chart
1570 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1571 // other means
1572
1573 if (m_bFollow) {
1574 tLat = gLat;
1575 tLon = gLon;
1576
1577 // Set the ViewPort center based on the OWNSHIP offset
1578 double dx = m_OSoffsetx;
1579 double dy = m_OSoffsety;
1580 double d_east = dx / GetVP().view_scale_ppm;
1581 double d_north = dy / GetVP().view_scale_ppm;
1582
1583 if (GetUpMode() == NORTH_UP_MODE) {
1584 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1585 } else {
1586 double offset_angle = atan2(d_north, d_east);
1587 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1588 double chart_angle = GetVPRotation();
1589 double target_angle = chart_angle + offset_angle;
1590 double d_east_mod = offset_distance * cos(target_angle);
1591 double d_north_mod = offset_distance * sin(target_angle);
1592 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1593 }
1594
1595 extern double gCog_gt;
1596
1597 // on lookahead mode, adjust the vp center point
1598 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1599 double cog_to_use = gCog;
1600 if (g_btenhertz &&
1601 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1602 cog_to_use = gCog_gt;
1603 blong_jump = true;
1604 }
1605 if (!g_btenhertz) cog_to_use = g_COGAvg;
1606
1607 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1608
1609 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1610 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1611
1612 double pixel_delta_tent =
1613 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1614
1615 double pixel_delta = 0;
1616
1617 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1618 // avoid jumping of the vp center point during slow maneuvering, or at
1619 // anchor....
1620 if (!std::isnan(gSog)) {
1621 if (gSog < 2.0)
1622 pixel_delta = 0.;
1623 else
1624 pixel_delta = pixel_delta_tent;
1625 }
1626
1627 meters_to_shift = 0;
1628 dir_to_shift = 0;
1629 if (!std::isnan(gCog)) {
1630 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1631 dir_to_shift = cog_to_use;
1632 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1633 &vpLon);
1634 } else {
1635 vpLat = gLat;
1636 vpLon = gLon;
1637 }
1638 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1639 m_OSoffsetx = 0; // center ownship on loss of GPS
1640 m_OSoffsety = 0;
1641 vpLat = gLat;
1642 vpLon = gLon;
1643 }
1644
1645 } else {
1646 tLat = m_vLat;
1647 tLon = m_vLon;
1648 vpLat = m_vLat;
1649 vpLon = m_vLon;
1650 }
1651
1652 if (GetQuiltMode()) {
1653 int current_db_index = -1;
1654 if (m_pCurrentStack)
1655 current_db_index =
1656 m_pCurrentStack
1657 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1658 // chart dbIndex
1659 else
1660 m_pCurrentStack = new ChartStack;
1661
1662 // This logic added to enable opening a chart when there is no
1663 // previous chart indication, either from inital startup, or from adding
1664 // new chart directory
1665 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1666 m_pCurrentStack) {
1667 if (m_pCurrentStack->nEntry) {
1668 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1669 1); // smallest scale
1670 SelectQuiltRefdbChart(new_dbIndex, true);
1671 m_bautofind = false;
1672 }
1673 }
1674
1675 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1676 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1677
1678 if (m_bFirstAuto) {
1679 // Allow the chart database to pre-calculate the MBTile inclusion test
1680 // boolean...
1681 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1682
1683 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1684 // physical pixels. On standard DPI displays where logical = physical
1685 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1686 // logical pixels, this ratio would be 0.5.
1687 double proposed_scale_onscreen =
1688 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1689
1690 int initial_db_index = m_restore_dbindex;
1691 if (initial_db_index < 0) {
1692 if (m_pCurrentStack->nEntry) {
1693 initial_db_index =
1694 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1695 } else
1696 m_bautofind = true; // initial_db_index = 0;
1697 }
1698
1699 if (m_pCurrentStack->nEntry) {
1700 int initial_type = ChartData->GetDBChartType(initial_db_index);
1701
1702 // Check to see if the target new chart is quiltable as a reference
1703 // chart
1704
1705 if (!IsChartQuiltableRef(initial_db_index)) {
1706 // If it is not quiltable, then walk the stack up looking for a
1707 // satisfactory chart i.e. one that is quiltable and of the same type
1708 // XXX if there's none?
1709 int stack_index = 0;
1710
1711 if (stack_index >= 0) {
1712 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1713 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1714 if (IsChartQuiltableRef(test_db_index) &&
1715 (initial_type ==
1716 ChartData->GetDBChartType(initial_db_index))) {
1717 initial_db_index = test_db_index;
1718 break;
1719 }
1720 stack_index++;
1721 }
1722 }
1723 }
1724
1725 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1726 if (pc) {
1727 SetQuiltRefChart(initial_db_index);
1728 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1729 }
1730 }
1731 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1732 // just GetVPScale(), so I'm not sure why it's necessary to define the
1733 // proposed_scale_onscreen variable.
1734 bNewView |= SetViewPoint(vpLat, vpLon,
1735 GetCanvasScaleFactor() / proposed_scale_onscreen,
1736 0, GetVPRotation());
1737 }
1738 // Measure rough jump distance if in bfollow mode
1739 // No good reason to do smooth pan for
1740 // jump distance more than one screen width at scale.
1741 bool super_jump = false;
1742 if (m_bFollow) {
1743 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1744 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1745 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1746 }
1747 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead) {
1748 int nstep = 5;
1749 if (blong_jump) nstep = 20;
1750 StartTimedMovementVP(vpLat, vpLon, nstep);
1751 } else {
1752 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1753 }
1754
1755 goto update_finish;
1756 }
1757
1758 // Single Chart Mode from here....
1759 pLast_Ch = m_singleChart;
1760 ChartTypeEnum new_open_type;
1761 ChartFamilyEnum new_open_family;
1762 if (pLast_Ch) {
1763 new_open_type = pLast_Ch->GetChartType();
1764 new_open_family = pLast_Ch->GetChartFamily();
1765 } else {
1766 new_open_type = CHART_TYPE_KAP;
1767 new_open_family = CHART_FAMILY_RASTER;
1768 }
1769
1770 bOpenSpecified = m_bFirstAuto;
1771
1772 // Make sure the target stack is valid
1773 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1774
1775 // Build a chart stack based on tLat, tLon
1776 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1777 m_groupIndex)) { // Bogus Lat, Lon?
1778 if (NULL == pDummyChart) {
1779 pDummyChart = new ChartDummy;
1780 bNewChart = true;
1781 }
1782
1783 if (m_singleChart)
1784 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1785
1786 m_singleChart = pDummyChart;
1787
1788 // If the current viewpoint is invalid, set the default scale to
1789 // something reasonable.
1790 double set_scale = GetVPScale();
1791 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1792
1793 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1794
1795 // If the chart stack has just changed, there is new status
1796 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1797 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1798 bNewPiano = true;
1799 bNewChart = true;
1800 }
1801 }
1802
1803 // Copy the new (by definition empty) stack into the target stack
1804 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1805
1806 goto update_finish;
1807 }
1808
1809 // Check to see if Chart Stack has changed
1810 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1811 // New chart stack, so...
1812 bNewPiano = true;
1813
1814 // Save a copy of the current stack
1815 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1816
1817 // Copy the new stack into the target stack
1818 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1819
1820 // Is Current Chart in new stack?
1821
1822 int tEntry = -1;
1823 if (NULL != m_singleChart) // this handles startup case
1824 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1825 m_singleChart->GetFullPath());
1826
1827 if (tEntry != -1) { // m_singleChart is in the new stack
1828 m_pCurrentStack->CurrentStackEntry = tEntry;
1829 bNewChart = false;
1830 }
1831
1832 else // m_singleChart is NOT in new stack
1833 { // So, need to open a new chart
1834 // Find the largest scale raster chart that opens OK
1835
1836 ChartBase *pProposed = NULL;
1837
1838 if (bCanvasChartAutoOpen) {
1839 bool search_direction =
1840 false; // default is to search from lowest to highest
1841 int start_index = 0;
1842
1843 // A special case: If panning at high scale, open largest scale
1844 // chart first
1845 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1846 (LastStack.nEntry == 0)) {
1847 search_direction = true;
1848 start_index = m_pCurrentStack->nEntry - 1;
1849 }
1850
1851 // Another special case, open specified index on program start
1852 if (bOpenSpecified) {
1853 search_direction = false;
1854 start_index = 0;
1855 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1856 start_index = 0;
1857
1858 new_open_type = CHART_TYPE_DONTCARE;
1859 }
1860
1861 pProposed = ChartData->OpenStackChartConditional(
1862 m_pCurrentStack, start_index, search_direction, new_open_type,
1863 new_open_family);
1864
1865 // Try to open other types/families of chart in some priority
1866 if (NULL == pProposed)
1867 pProposed = ChartData->OpenStackChartConditional(
1868 m_pCurrentStack, start_index, search_direction,
1869 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1870
1871 if (NULL == pProposed)
1872 pProposed = ChartData->OpenStackChartConditional(
1873 m_pCurrentStack, start_index, search_direction,
1874 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1875
1876 bNewChart = true;
1877
1878 } // bCanvasChartAutoOpen
1879
1880 else
1881 pProposed = NULL;
1882
1883 // If no go, then
1884 // Open a Dummy Chart
1885 if (NULL == pProposed) {
1886 if (NULL == pDummyChart) {
1887 pDummyChart = new ChartDummy;
1888 bNewChart = true;
1889 }
1890
1891 if (pLast_Ch)
1892 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1893
1894 pProposed = pDummyChart;
1895 }
1896
1897 // Arriving here, pProposed points to an opened chart, or NULL.
1898 if (m_singleChart) m_singleChart->Deactivate();
1899 m_singleChart = pProposed;
1900
1901 if (m_singleChart) {
1902 m_singleChart->Activate();
1903 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1904 m_pCurrentStack, m_singleChart->GetFullPath());
1905 }
1906 } // need new chart
1907
1908 // Arriving here, m_singleChart is opened and OK, or NULL
1909 if (NULL != m_singleChart) {
1910 // Setup the view using the current scale
1911 double set_scale = GetVPScale();
1912
1913 // If the current viewpoint is invalid, set the default scale to
1914 // something reasonable.
1915 if (!GetVP().IsValid())
1916 set_scale = 1. / 20000.;
1917 else { // otherwise, match scale if elected.
1918 double proposed_scale_onscreen;
1919
1920 if (m_bFollow) { // autoset the scale only if in autofollow
1921 double new_scale_ppm =
1922 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1923 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1924 } else
1925 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1926
1927 // This logic will bring a new chart onscreen at roughly twice the true
1928 // paper scale equivalent. Note that first chart opened on application
1929 // startup (bOpenSpecified = true) will open at the config saved scale
1930 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1931 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1932 double equivalent_vp_scale =
1933 GetCanvasScaleFactor() / proposed_scale_onscreen;
1934 double new_scale_ppm =
1935 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1936 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1937 }
1938
1939 if (m_bFollow) { // bounds-check the scale only if in autofollow
1940 proposed_scale_onscreen =
1941 wxMin(proposed_scale_onscreen,
1942 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1943 GetCanvasWidth()));
1944 proposed_scale_onscreen =
1945 wxMax(proposed_scale_onscreen,
1946 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1947 g_b_overzoom_x));
1948 }
1949
1950 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1951 }
1952
1953 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1954 m_singleChart->GetChartSkew() * PI / 180.,
1955 GetVPRotation());
1956 }
1957 } // new stack
1958
1959 else // No change in Chart Stack
1960 {
1961 if ((m_bFollow) && m_singleChart)
1962 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1963 m_singleChart->GetChartSkew() * PI / 180.,
1964 GetVPRotation());
1965 }
1966
1967update_finish:
1968
1969 // TODO
1970 // if( bNewPiano ) UpdateControlBar();
1971
1972 m_bFirstAuto = false; // Auto open on program start
1973
1974 // If we need a Refresh(), do it here...
1975 // But don't duplicate a Refresh() done by SetViewPoint()
1976 if (bNewChart && !bNewView) Refresh(false);
1977
1978#ifdef ocpnUSE_GL
1979 // If a new chart, need to invalidate gl viewport for refresh
1980 // so the fbo gets flushed
1981 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1982#endif
1983
1984 return bNewChart | bNewView;
1985}
1986
1987void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1988 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1989
1990 SetQuiltRefChart(db_index);
1991 if (ChartData) {
1992 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1993 if (pc) {
1994 if (b_autoscale) {
1995 double best_scale_ppm = GetBestVPScale(pc);
1996 SetVPScale(best_scale_ppm);
1997 }
1998 } else
1999 SetQuiltRefChart(-1);
2000 } else
2001 SetQuiltRefChart(-1);
2002}
2003
2004void ChartCanvas::SelectQuiltRefChart(int selected_index) {
2005 std::vector<int> piano_chart_index_array =
2006 GetQuiltExtendedStackdbIndexArray();
2007 int current_db_index = piano_chart_index_array[selected_index];
2008
2009 SelectQuiltRefdbChart(current_db_index);
2010}
2011
2012double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
2013 if (pchart) {
2014 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
2015
2016 if ((g_bPreserveScaleOnX) ||
2017 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2018 double new_scale_ppm = GetVPScale();
2019 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2020 } else {
2021 // This logic will bring the new chart onscreen at roughly twice the true
2022 // paper scale equivalent.
2023 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2024 double equivalent_vp_scale =
2025 GetCanvasScaleFactor() / proposed_scale_onscreen;
2026 double new_scale_ppm =
2027 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2028 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2029 }
2030
2031 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2032 // set. Otherwise, we get severe performance problems on all platforms
2033
2034 double max_underzoom_multiplier = 2.0;
2035 if (GetVP().b_quilt) {
2036 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2037 pchart->GetChartType(),
2038 pchart->GetChartFamily());
2039 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2040 }
2041
2042 proposed_scale_onscreen = wxMin(
2043 proposed_scale_onscreen,
2044 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2045 max_underzoom_multiplier);
2046
2047 // And, do not allow excessive overzoom either
2048 proposed_scale_onscreen =
2049 wxMax(proposed_scale_onscreen,
2050 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2051
2052 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2053 } else
2054 return 1.0;
2055}
2056
2057void ChartCanvas::SetupCanvasQuiltMode(void) {
2058 if (GetQuiltMode()) // going to quilt mode
2059 {
2060 ChartData->LockCache();
2061
2062 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2063
2064 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2065
2066 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2067 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2068 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2069 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2070
2071 m_Piano->SetRoundedRectangles(true);
2072
2073 // Select the proper Ref chart
2074 int target_new_dbindex = -1;
2075 if (m_pCurrentStack) {
2076 target_new_dbindex =
2077 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2078
2079 if (-1 != target_new_dbindex) {
2080 if (!IsChartQuiltableRef(target_new_dbindex)) {
2081 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2082 int type = ChartData->GetDBChartType(target_new_dbindex);
2083
2084 // walk the stack up looking for a satisfactory chart
2085 int stack_index = m_pCurrentStack->CurrentStackEntry;
2086
2087 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2088 (stack_index >= 0)) {
2089 int proj_tent = ChartData->GetDBChartProj(
2090 m_pCurrentStack->GetDBIndex(stack_index));
2091 int type_tent = ChartData->GetDBChartType(
2092 m_pCurrentStack->GetDBIndex(stack_index));
2093
2094 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2095 if ((proj == proj_tent) && (type_tent == type)) {
2096 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2097 break;
2098 }
2099 }
2100 stack_index++;
2101 }
2102 }
2103 }
2104 }
2105
2106 if (IsChartQuiltableRef(target_new_dbindex))
2107 SelectQuiltRefdbChart(target_new_dbindex,
2108 false); // Try not to allow a scale change
2109 else
2110 SelectQuiltRefdbChart(-1, false);
2111
2112 m_singleChart = NULL; // Bye....
2113
2114 // Re-qualify the quilt reference chart selection
2115 AdjustQuiltRefChart();
2116
2117 // Restore projection type saved on last quilt mode toggle
2118 // TODO
2119 // if(g_sticky_projection != -1)
2120 // GetVP().SetProjectionType(g_sticky_projection);
2121 // else
2122 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2123 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2124
2125 } else // going to SC Mode
2126 {
2127 std::vector<int> empty_array;
2128 m_Piano->SetActiveKeyArray(empty_array);
2129 m_Piano->SetNoshowIndexArray(empty_array);
2130 m_Piano->SetEclipsedIndexArray(empty_array);
2131
2132 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2133 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon(_T("viz"))));
2134 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon(_T("redX"))));
2135 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon(_T("tmercprj"))));
2136 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon(_T("skewprj"))));
2137
2138 m_Piano->SetRoundedRectangles(false);
2139 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2140 }
2141
2142 // When shifting from quilt to single chart mode, select the "best" single
2143 // chart to show
2144 if (!GetQuiltMode()) {
2145 if (ChartData && ChartData->IsValid()) {
2146 UnlockQuilt();
2147
2148 double tLat, tLon;
2149 if (m_bFollow == true) {
2150 tLat = gLat;
2151 tLon = gLon;
2152 } else {
2153 tLat = m_vLat;
2154 tLon = m_vLon;
2155 }
2156
2157 if (!m_singleChart) {
2158 // Build a temporary chart stack based on tLat, tLon
2159 ChartStack TempStack;
2160 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2161 m_groupIndex);
2162
2163 // Iterate over the quilt charts actually shown, looking for the
2164 // largest scale chart that will be in the new chartstack.... This
2165 // will (almost?) always be the reference chart....
2166
2167 ChartBase *Candidate_Chart = NULL;
2168 int cur_max_scale = (int)1e8;
2169
2170 ChartBase *pChart = GetFirstQuiltChart();
2171 while (pChart) {
2172 // Is this pChart in new stack?
2173 int tEntry =
2174 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2175 if (tEntry != -1) {
2176 if (pChart->GetNativeScale() < cur_max_scale) {
2177 Candidate_Chart = pChart;
2178 cur_max_scale = pChart->GetNativeScale();
2179 }
2180 }
2181 pChart = GetNextQuiltChart();
2182 }
2183
2184 m_singleChart = Candidate_Chart;
2185
2186 // If the quilt is empty, there is no "best" chart.
2187 // So, open the smallest scale chart in the current stack
2188 if (NULL == m_singleChart) {
2189 m_singleChart = ChartData->OpenStackChartConditional(
2190 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2191 CHART_FAMILY_DONTCARE);
2192 }
2193 }
2194
2195 // Invalidate all the charts in the quilt,
2196 // as any cached data may be region based and not have fullscreen coverage
2197 InvalidateAllQuiltPatchs();
2198
2199 if (m_singleChart) {
2200 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2201 std::vector<int> one_array;
2202 one_array.push_back(dbi);
2203 m_Piano->SetActiveKeyArray(one_array);
2204 }
2205
2206 if (m_singleChart) {
2207 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2208 }
2209 }
2210 // Invalidate the current stack so that it will be rebuilt on next tick
2211 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2212 }
2213}
2214
2215bool ChartCanvas::IsTempMenuBarEnabled() {
2216#ifdef __WXMSW__
2217 int major;
2218 wxGetOsVersion(&major);
2219 return (major >
2220 5); // For Windows, function is only available on Vista and above
2221#else
2222 return true;
2223#endif
2224}
2225
2226double ChartCanvas::GetCanvasRangeMeters() {
2227 int width, height;
2228 GetSize(&width, &height);
2229 int minDimension = wxMin(width, height);
2230
2231 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2232 range *= cos(GetVP().clat * PI / 180.);
2233 return range;
2234}
2235
2236void ChartCanvas::SetCanvasRangeMeters(double range) {
2237 int width, height;
2238 GetSize(&width, &height);
2239 int minDimension = wxMin(width, height);
2240
2241 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2242 SetVPScale(scale_ppm / 2);
2243}
2244
2245bool ChartCanvas::SetUserOwnship() {
2246 // Look for user defined ownship image
2247 // This may be found in the shared data location along with other user
2248 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2249 if (pWayPointMan && pWayPointMan->DoesIconExist(_T("ownship"))) {
2250 double factor_dusk = 0.5;
2251 double factor_night = 0.25;
2252
2253 wxBitmap *pbmp = pWayPointMan->GetIconBitmap(_T("ownship"));
2254 m_pos_image_user_day = new wxImage;
2255 *m_pos_image_user_day = pbmp->ConvertToImage();
2256 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2257
2258 int gimg_width = m_pos_image_user_day->GetWidth();
2259 int gimg_height = m_pos_image_user_day->GetHeight();
2260
2261 // Make dusk and night images
2262 m_pos_image_user_dusk = new wxImage;
2263 m_pos_image_user_night = new wxImage;
2264
2265 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2266 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2267
2268 for (int iy = 0; iy < gimg_height; iy++) {
2269 for (int ix = 0; ix < gimg_width; ix++) {
2270 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2271 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2272 m_pos_image_user_day->GetGreen(ix, iy),
2273 m_pos_image_user_day->GetBlue(ix, iy));
2274 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2275 hsv.value = hsv.value * factor_dusk;
2276 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2277 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2278 nrgb.blue);
2279
2280 hsv = wxImage::RGBtoHSV(rgb);
2281 hsv.value = hsv.value * factor_night;
2282 nrgb = wxImage::HSVtoRGB(hsv);
2283 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2284 nrgb.blue);
2285 }
2286 }
2287 }
2288
2289 // Make some alternate greyed out day/dusk/night images
2290 m_pos_image_user_grey_day = new wxImage;
2291 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2292
2293 m_pos_image_user_grey_dusk = new wxImage;
2294 m_pos_image_user_grey_night = new wxImage;
2295
2296 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2297 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2298
2299 for (int iy = 0; iy < gimg_height; iy++) {
2300 for (int ix = 0; ix < gimg_width; ix++) {
2301 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2302 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2303 m_pos_image_user_grey_day->GetGreen(ix, iy),
2304 m_pos_image_user_grey_day->GetBlue(ix, iy));
2305 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2306 hsv.value = hsv.value * factor_dusk;
2307 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2308 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2309 nrgb.blue);
2310
2311 hsv = wxImage::RGBtoHSV(rgb);
2312 hsv.value = hsv.value * factor_night;
2313 nrgb = wxImage::HSVtoRGB(hsv);
2314 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2315 nrgb.blue);
2316 }
2317 }
2318 }
2319
2320 // Make a yellow image for rendering under low accuracy chart conditions
2321 m_pos_image_user_yellow_day = new wxImage;
2322 m_pos_image_user_yellow_dusk = new wxImage;
2323 m_pos_image_user_yellow_night = new wxImage;
2324
2325 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2326 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2327 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2328
2329 for (int iy = 0; iy < gimg_height; iy++) {
2330 for (int ix = 0; ix < gimg_width; ix++) {
2331 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2332 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2333 m_pos_image_user_grey_day->GetGreen(ix, iy),
2334 m_pos_image_user_grey_day->GetBlue(ix, iy));
2335
2336 // Simply remove all "blue" from the greyscaled image...
2337 // so, what is not black becomes yellow.
2338 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2339 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2340 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2341
2342 hsv = wxImage::RGBtoHSV(rgb);
2343 hsv.value = hsv.value * factor_dusk;
2344 nrgb = wxImage::HSVtoRGB(hsv);
2345 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2346
2347 hsv = wxImage::RGBtoHSV(rgb);
2348 hsv.value = hsv.value * factor_night;
2349 nrgb = wxImage::HSVtoRGB(hsv);
2350 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2351 0);
2352 }
2353 }
2354 }
2355
2356 return true;
2357 } else
2358 return false;
2359}
2360
2362 m_display_size_mm = size;
2363
2364 // int sx, sy;
2365 // wxDisplaySize( &sx, &sy );
2366
2367 // Calculate logical pixels per mm for later reference.
2368 wxSize sd = g_Platform->getDisplaySize();
2369 double horizontal = sd.x;
2370 // Set DPI (Win) scale factor
2371 g_scaler = g_Platform->GetDisplayDIPMult(this);
2372
2373 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2374 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2375
2376 if (ps52plib) {
2377 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2378 ps52plib->SetPPMM(m_pix_per_mm);
2379 }
2380
2381 wxString msg;
2382 msg.Printf(
2383 _T("Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): ")
2384 _T("%d:%d "),
2385 m_display_size_mm, sd.x, sd.y);
2386 wxLogMessage(msg);
2387
2388 int ssx, ssy;
2389 ssx = g_monitor_info[g_current_monitor].width;
2390 ssy = g_monitor_info[g_current_monitor].height;
2391 msg.Printf(_T("monitor size: %d %d"), ssx, ssy);
2392 wxLogMessage(msg);
2393
2394 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2395}
2396#if 0
2397void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2398{
2399 wxString msg(event.m_string.c_str(), wxConvUTF8);
2400 // if cpus are removed between runs
2401 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2402 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2403 }
2404
2405 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2406 {
2407 compress_msg_array.RemoveAt(event.thread);
2408 compress_msg_array.Insert( msg, event.thread);
2409 }
2410 else
2411 compress_msg_array.Add(msg);
2412
2413
2414 wxString combined_msg;
2415 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2416 combined_msg += compress_msg_array[i];
2417 combined_msg += _T("\n");
2418 }
2419
2420 bool skip = false;
2421 pprog->Update(pprog_count, combined_msg, &skip );
2422 pprog->SetSize(pprog_size);
2423 if(skip)
2424 b_skipout = skip;
2425}
2426#endif
2427void ChartCanvas::InvalidateGL() {
2428 if (!m_glcc) return;
2429#ifdef ocpnUSE_GL
2430 if (g_bopengl) m_glcc->Invalidate();
2431#endif
2432 if (m_Compass) m_Compass->UpdateStatus(true);
2433}
2434
2435int ChartCanvas::GetCanvasChartNativeScale() {
2436 int ret = 1;
2437 if (!VPoint.b_quilt) {
2438 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2439 } else
2440 ret = (int)m_pQuilt->GetRefNativeScale();
2441
2442 return ret;
2443}
2444
2445ChartBase *ChartCanvas::GetChartAtCursor() {
2446 ChartBase *target_chart;
2447 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2448 target_chart = m_singleChart;
2449 else if (VPoint.b_quilt)
2450 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2451 else
2452 target_chart = NULL;
2453 return target_chart;
2454}
2455
2456ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2457 ChartBase *target_chart;
2458 if (VPoint.b_quilt)
2459 target_chart =
2460 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2461 else
2462 target_chart = NULL;
2463 return target_chart;
2464}
2465
2466int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2467 int new_dbIndex = -1;
2468 if (!VPoint.b_quilt) {
2469 if (m_pCurrentStack) {
2470 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2471 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2472 if (sc >= scale) {
2473 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2474 break;
2475 }
2476 }
2477 }
2478 } else {
2479 // Using the current quilt, select a useable reference chart
2480 // Said chart will be in the extended (possibly full-screen) stack,
2481 // And will have a scale equal to or just greater than the stipulated
2482 // value
2483 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2484 if (im > 0) {
2485 for (unsigned int is = 0; is < im; is++) {
2486 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2487 m_pQuilt->GetExtendedStackIndexArray()[is]);
2488 if ((m.Scale_ge(
2489 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2490 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2491 break;
2492 }
2493 }
2494 }
2495 }
2496
2497 return new_dbIndex;
2498}
2499
2500void ChartCanvas::EnablePaint(bool b_enable) {
2501 m_b_paint_enable = b_enable;
2502#ifdef ocpnUSE_GL
2503 if (m_glcc) m_glcc->EnablePaint(b_enable);
2504#endif
2505}
2506
2507bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2508
2509void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2510
2511std::vector<int> ChartCanvas::GetQuiltIndexArray(void) {
2512 return m_pQuilt->GetQuiltIndexArray();
2513 ;
2514}
2515
2516void ChartCanvas::SetQuiltMode(bool b_quilt) {
2517 VPoint.b_quilt = b_quilt;
2518 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2519}
2520
2521bool ChartCanvas::GetQuiltMode(void) { return VPoint.b_quilt; }
2522
2523int ChartCanvas::GetQuiltReferenceChartIndex(void) {
2524 return m_pQuilt->GetRefChartdbIndex();
2525}
2526
2527void ChartCanvas::InvalidateAllQuiltPatchs(void) {
2528 m_pQuilt->InvalidateAllQuiltPatchs();
2529}
2530
2531ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2532 return m_pQuilt->GetLargestScaleChart();
2533}
2534
2535ChartBase *ChartCanvas::GetFirstQuiltChart() {
2536 return m_pQuilt->GetFirstChart();
2537}
2538
2539ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2540
2541int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2542
2543void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2544 m_pQuilt->SetHiliteIndex(dbIndex);
2545}
2546
2547void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2548 m_pQuilt->SetHiliteIndexArray(hilite_array);
2549}
2550
2551void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2552 m_pQuilt->ClearHiliteIndexArray();
2553}
2554
2555std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2556 bool flag2) {
2557 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2558}
2559
2560int ChartCanvas::GetQuiltRefChartdbIndex(void) {
2561 return m_pQuilt->GetRefChartdbIndex();
2562}
2563
2564std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2565 return m_pQuilt->GetExtendedStackIndexArray();
2566}
2567
2568std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2569 return m_pQuilt->GetFullscreenIndexArray();
2570}
2571
2572std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2573 return m_pQuilt->GetEclipsedStackIndexArray();
2574}
2575
2576void ChartCanvas::InvalidateQuilt(void) { return m_pQuilt->Invalidate(); }
2577
2578double ChartCanvas::GetQuiltMaxErrorFactor() {
2579 return m_pQuilt->GetMaxErrorFactor();
2580}
2581
2582bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2583 return m_pQuilt->IsChartQuiltableRef(db_index);
2584}
2585
2586bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2587 double chartMaxScale =
2588 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2589 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2590}
2591
2592void ChartCanvas::StartMeasureRoute() {
2593 if (!m_routeState) { // no measure tool if currently creating route
2594 if (m_bMeasure_Active) {
2595 g_pRouteMan->DeleteRoute(m_pMeasureRoute,
2596 NavObjectChanges::getInstance());
2597 m_pMeasureRoute = NULL;
2598 }
2599
2600 m_bMeasure_Active = true;
2601 m_nMeasureState = 1;
2602 m_bDrawingRoute = false;
2603
2604 SetCursor(*pCursorPencil);
2605 Refresh();
2606 }
2607}
2608
2609void ChartCanvas::CancelMeasureRoute() {
2610 m_bMeasure_Active = false;
2611 m_nMeasureState = 0;
2612 m_bDrawingRoute = false;
2613
2614 g_pRouteMan->DeleteRoute(m_pMeasureRoute, NavObjectChanges::getInstance());
2615 m_pMeasureRoute = NULL;
2616
2617 SetCursor(*pCursorArrow);
2618}
2619
2620ViewPort &ChartCanvas::GetVP() { return VPoint; }
2621
2622void ChartCanvas::SetVP(ViewPort &vp) {
2623 VPoint = vp;
2624 VPoint.SetPixelScale(m_displayScale);
2625}
2626
2627// void ChartCanvas::SetFocus()
2628// {
2629// printf("set %d\n", m_canvasIndex);
2630// //wxWindow:SetFocus();
2631// }
2632
2633void ChartCanvas::TriggerDeferredFocus() {
2634 // #if defined(__WXGTK__) || defined(__WXOSX__)
2635
2636 m_deferredFocusTimer.Start(20, true);
2637
2638#if defined(__WXGTK__) || defined(__WXOSX__)
2639 gFrame->Raise();
2640#endif
2641
2642 // gFrame->Raise();
2643 // #else
2644 // SetFocus();
2645 // Refresh(true);
2646 // #endif
2647}
2648
2649void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2650 SetFocus();
2651 Refresh(true);
2652}
2653
2654void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2655 if (SendKeyEventToPlugins(event))
2656 return; // PlugIn did something, and does not want the canvas to do
2657 // anything else
2658
2659 int key_char = event.GetKeyCode();
2660 switch (key_char) {
2661 case '?':
2662 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2663 break;
2664 case '+':
2665 ZoomCanvas(g_plus_minus_zoom_factor, false);
2666 break;
2667 case '-':
2668 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2669 break;
2670 default:
2671 break;
2672 }
2673 if (g_benable_rotate) {
2674 switch (key_char) {
2675 case ']':
2676 RotateCanvas(1);
2677 Refresh();
2678 break;
2679
2680 case '[':
2681 RotateCanvas(-1);
2682 Refresh();
2683 break;
2684
2685 case '\\':
2686 DoRotateCanvas(0);
2687 break;
2688 }
2689 }
2690
2691 event.Skip();
2692}
2693
2694void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2695 if (SendKeyEventToPlugins(event))
2696 return; // PlugIn did something, and does not want the canvas to do
2697 // anything else
2698
2699 bool b_handled = false;
2700
2701 m_modkeys = event.GetModifiers();
2702
2703 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2704
2705#ifdef OCPN_ALT_MENUBAR
2706#ifndef __WXOSX__
2707 // If the permanent menubar is disabled, we show it temporarily when Alt is
2708 // pressed or when Alt + a letter is presssed (for the top-menu-level
2709 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2710 // some special cases.
2711 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2712 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2713 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2714 if (!g_bTempShowMenuBar) {
2715 g_bTempShowMenuBar = true;
2716 parent_frame->ApplyGlobalSettings(false);
2717 }
2718 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2719 event.Skip();
2720 return;
2721 }
2722 // If another key is pressed while Alt is down, do NOT toggle the menus when
2723 // Alt is released
2724 if (event.GetKeyCode() != WXK_ALT) {
2725 m_bMayToggleMenuBar = false;
2726 }
2727 }
2728#endif
2729#endif
2730
2731 // HOTKEYS
2732 switch (event.GetKeyCode()) {
2733 case WXK_TAB:
2734 // parent_frame->SwitchKBFocus( this );
2735 break;
2736
2737 case WXK_MENU:
2738 int x, y;
2739 event.GetPosition(&x, &y);
2740 m_FinishRouteOnKillFocus = false;
2741 CallPopupMenu(x, y);
2742 m_FinishRouteOnKillFocus = true;
2743 break;
2744
2745 case WXK_ALT:
2746 m_modkeys |= wxMOD_ALT;
2747 break;
2748
2749 case WXK_CONTROL:
2750 m_modkeys |= wxMOD_CONTROL;
2751 break;
2752
2753#ifdef __WXOSX__
2754 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2755 case WXK_RAW_CONTROL:
2756 m_modkeys |= wxMOD_RAW_CONTROL;
2757 break;
2758#endif
2759
2760 case WXK_LEFT:
2761 if (m_modkeys == wxMOD_CONTROL)
2762 parent_frame->DoStackDown(this);
2763 else if (g_bsmoothpanzoom) {
2764 StartTimedMovement();
2765 m_panx = -1;
2766 } else {
2767 PanCanvas(-panspeed, 0);
2768 }
2769 b_handled = true;
2770 break;
2771
2772 case WXK_UP:
2773 if (g_bsmoothpanzoom) {
2774 StartTimedMovement();
2775 m_pany = -1;
2776 } else
2777 PanCanvas(0, -panspeed);
2778 b_handled = true;
2779 break;
2780
2781 case WXK_RIGHT:
2782 if (m_modkeys == wxMOD_CONTROL)
2783 parent_frame->DoStackUp(this);
2784 else if (g_bsmoothpanzoom) {
2785 StartTimedMovement();
2786 m_panx = 1;
2787 } else
2788 PanCanvas(panspeed, 0);
2789 b_handled = true;
2790
2791 break;
2792
2793 case WXK_DOWN:
2794 if (g_bsmoothpanzoom) {
2795 StartTimedMovement();
2796 m_pany = 1;
2797 } else
2798 PanCanvas(0, panspeed);
2799 b_handled = true;
2800 break;
2801
2802 case WXK_F2:
2803 TogglebFollow();
2804 break;
2805
2806 case WXK_F3: {
2807 SetShowENCText(!GetShowENCText());
2808 Refresh(true);
2809 InvalidateGL();
2810 break;
2811 }
2812 case WXK_F4:
2813 if (!m_bMeasure_Active) {
2814 if (event.ShiftDown())
2815 m_bMeasure_DistCircle = true;
2816 else
2817 m_bMeasure_DistCircle = false;
2818
2819 StartMeasureRoute();
2820 } else {
2821 CancelMeasureRoute();
2822
2823 SetCursor(*pCursorArrow);
2824
2825 // SurfaceToolbar();
2826 InvalidateGL();
2827 Refresh(false);
2828 }
2829
2830 break;
2831
2832 case WXK_F5:
2833 parent_frame->ToggleColorScheme();
2834 gFrame->Raise();
2835 TriggerDeferredFocus();
2836 break;
2837
2838 case WXK_F6: {
2839 int mod = m_modkeys & wxMOD_SHIFT;
2840 if (mod != m_brightmod) {
2841 m_brightmod = mod;
2842 m_bbrightdir = !m_bbrightdir;
2843 }
2844
2845 if (!m_bbrightdir) {
2846 g_nbrightness -= 10;
2847 if (g_nbrightness <= MIN_BRIGHT) {
2848 g_nbrightness = MIN_BRIGHT;
2849 m_bbrightdir = true;
2850 }
2851 } else {
2852 g_nbrightness += 10;
2853 if (g_nbrightness >= MAX_BRIGHT) {
2854 g_nbrightness = MAX_BRIGHT;
2855 m_bbrightdir = false;
2856 }
2857 }
2858
2859 SetScreenBrightness(g_nbrightness);
2860 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2861
2862 SetFocus(); // just in case the external program steals it....
2863 gFrame->Raise(); // And reactivate the application main
2864
2865 break;
2866 }
2867
2868 case WXK_F7:
2869 parent_frame->DoStackDown(this);
2870 break;
2871
2872 case WXK_F8:
2873 parent_frame->DoStackUp(this);
2874 break;
2875
2876#ifndef __WXOSX__
2877 case WXK_F9: {
2878 double t0 = wxGetLocalTimeMillis().ToDouble();
2879 pConfig->Flush();
2880 double t1 = wxGetLocalTimeMillis().ToDouble() - t0;
2881
2882 ToggleCanvasQuiltMode();
2883 break;
2884 }
2885#endif
2886
2887 case WXK_F11:
2888 parent_frame->ToggleFullScreen();
2889 b_handled = true;
2890 break;
2891
2892 case WXK_F12: {
2893 if (m_modkeys == wxMOD_ALT) {
2894 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2895 // testing
2896 bool b = GetEnableTenHertzUpdate();
2897 EnableTenHertzUpdate(!b);
2898 UpdateGPSCompassStatusBox(true);
2899 } else
2900 ToggleChartOutlines();
2901 break;
2902 }
2903
2904 case WXK_PAUSE: // Drop MOB
2905 parent_frame->ActivateMOB();
2906 break;
2907
2908 // NUMERIC PAD
2909 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2910 case WXK_PAGEUP: {
2911 ZoomCanvas(g_plus_minus_zoom_factor, false);
2912 break;
2913 }
2914 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2915 case WXK_PAGEDOWN: {
2916 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2917 break;
2918 }
2919 case WXK_DELETE:
2920 case WXK_BACK:
2921 if (m_bMeasure_Active) {
2922 if (m_nMeasureState > 2) {
2923 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2924 m_pMeasureRoute->m_lastMousePointIndex =
2925 m_pMeasureRoute->GetnPoints();
2926 m_nMeasureState--;
2927 gFrame->RefreshAllCanvas();
2928 } else {
2929 CancelMeasureRoute();
2930 StartMeasureRoute();
2931 }
2932 }
2933 break;
2934 default:
2935 break;
2936 }
2937
2938 if (event.GetKeyCode() < 128) // ascii
2939 {
2940 int key_char = event.GetKeyCode();
2941
2942 // Handle both QWERTY and AZERTY keyboard separately for a few control
2943 // codes
2944 if (!g_b_assume_azerty) {
2945#ifdef __WXMAC__
2946 if (g_benable_rotate) {
2947 switch (key_char) {
2948 // On other platforms these are handled in OnKeyChar, which
2949 // (apparently) works better in some locales. On OS X it is better
2950 // to handle them here, since pressing Alt (which should change the
2951 // rotation speed) changes the key char and so prevents the keys
2952 // from working.
2953 case ']':
2954 RotateCanvas(1);
2955 b_handled = true;
2956 break;
2957
2958 case '[':
2959 RotateCanvas(-1);
2960 b_handled = true;
2961 break;
2962
2963 case '\\':
2964 DoRotateCanvas(0);
2965 b_handled = true;
2966 break;
2967 }
2968 }
2969#endif
2970 } else { // AZERTY
2971 switch (key_char) {
2972 case 43:
2973 ZoomCanvas(g_plus_minus_zoom_factor, false);
2974 break;
2975
2976 case 54: // '-' alpha/num pad
2977 // case 56: // '_' alpha/num pad
2978 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2979 break;
2980 }
2981 }
2982
2983#ifdef __WXOSX__
2984 // Ctrl+Cmd+F toggles fullscreen on macOS
2985 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
2986 m_modkeys & wxMOD_RAW_CONTROL) {
2987 parent_frame->ToggleFullScreen();
2988 return;
2989 }
2990#endif
2991
2992 if (event.ControlDown()) key_char -= 64;
2993
2994 if (key_char >= '0' && key_char <= '9')
2995 SetGroupIndex(key_char - '0');
2996 else
2997
2998 switch (key_char) {
2999 case 'A':
3000 SetShowENCAnchor(!GetShowENCAnchor());
3001 ReloadVP();
3002
3003 break;
3004
3005 case 'C':
3006 parent_frame->ToggleColorScheme();
3007 break;
3008
3009 case 'D': {
3010 int x, y;
3011 event.GetPosition(&x, &y);
3012 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3013 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3014 // First find out what kind of chart is being used
3015 if (!pPopupDetailSlider) {
3016 if (VPoint.b_quilt) {
3017 if (m_pQuilt) {
3018 if (m_pQuilt->GetChartAtPix(
3019 VPoint,
3020 wxPoint(
3021 x, y))) // = null if no chart loaded for this point
3022 {
3023 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3024 ->GetChartType();
3025 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3026 ->GetChartFamily();
3027 }
3028 }
3029 } else {
3030 if (m_singleChart) {
3031 ChartType = m_singleChart->GetChartType();
3032 ChartFam = m_singleChart->GetChartFamily();
3033 }
3034 }
3035 // If a charttype is found show the popupslider
3036 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3037 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3038 pPopupDetailSlider = new PopUpDSlide(
3039 this, -1, ChartType, ChartFam,
3040 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3041 wxDefaultSize, wxSIMPLE_BORDER, _T(""));
3042 if (pPopupDetailSlider) pPopupDetailSlider->Show();
3043 }
3044 } else //( !pPopupDetailSlider ) close popupslider
3045 {
3046 if (pPopupDetailSlider) pPopupDetailSlider->Close();
3047 pPopupDetailSlider = NULL;
3048 }
3049 break;
3050 }
3051
3052 case 'E':
3053 if (!wxWindow::FindWindowByName("NmeaDebugWindow")) {
3054 auto top_window = wxWindow::FindWindowByName(kTopLevelWindowName);
3055 NMEALogWindow::GetInstance().Create(top_window, 35);
3056 }
3057 wxWindow::FindWindowByName("NmeaDebugWindow")->Show();
3058 break;
3059
3060 case 'L':
3061 SetShowENCLights(!GetShowENCLights());
3062 ReloadVP();
3063
3064 break;
3065
3066 case 'M':
3067 if (event.ShiftDown())
3068 m_bMeasure_DistCircle = true;
3069 else
3070 m_bMeasure_DistCircle = false;
3071
3072 StartMeasureRoute();
3073 break;
3074
3075 case 'N':
3076 if (g_bInlandEcdis && ps52plib) {
3077 SetENCDisplayCategory((_DisCat)STANDARD);
3078 }
3079 break;
3080
3081 case 'O':
3082 ToggleChartOutlines();
3083 break;
3084
3085 case 'Q':
3086 ToggleCanvasQuiltMode();
3087 break;
3088
3089 case 'P':
3090 parent_frame->ToggleTestPause();
3091 break;
3092 case 'R':
3093 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3094 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3095 g_iNavAidRadarRingsNumberVisible = 1;
3096 else if (!g_bNavAidRadarRingsShown &&
3097 g_iNavAidRadarRingsNumberVisible == 1)
3098 g_iNavAidRadarRingsNumberVisible = 0;
3099 break;
3100 case 'S':
3101 SetShowENCDepth(!m_encShowDepth);
3102 ReloadVP();
3103 break;
3104
3105 case 'T':
3106 SetShowENCText(!GetShowENCText());
3107 ReloadVP();
3108 break;
3109
3110 case 'U':
3111 SetShowENCDataQual(!GetShowENCDataQual());
3112 ReloadVP();
3113 break;
3114
3115 case 'V':
3116 m_bShowNavobjects = !m_bShowNavobjects;
3117 Refresh(true);
3118 break;
3119
3120 case 'W': // W Toggle CPA alarm
3121 ToggleCPAWarn();
3122
3123 break;
3124
3125 case 1: // Ctrl A
3126 TogglebFollow();
3127
3128 break;
3129
3130 case 2: // Ctrl B
3131 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3132 break;
3133
3134 case 13: // Ctrl M // Drop Marker at cursor
3135 {
3136 if (event.ControlDown()) gFrame->DropMarker(false);
3137 break;
3138 }
3139
3140 case 14: // Ctrl N - Activate next waypoint in a route
3141 {
3142 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3143 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3144 if ((indexActive + 1) <= r->GetnPoints()) {
3145 g_pRouteMan->ActivateNextPoint(r, true);
3146 InvalidateGL();
3147 Refresh(false);
3148 }
3149 }
3150 break;
3151 }
3152
3153 case 15: // Ctrl O - Drop Marker at boat's position
3154 {
3155 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3156 break;
3157 }
3158
3159 case 32: // Special needs use space bar
3160 {
3161 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3162 break;
3163 }
3164
3165 case -32: // Ctrl Space // Drop MOB
3166 {
3167 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3168
3169 break;
3170 }
3171
3172 case -20: // Ctrl ,
3173 {
3174 parent_frame->DoSettings();
3175 break;
3176 }
3177 case 17: // Ctrl Q
3178 parent_frame->Close();
3179 return;
3180
3181 case 18: // Ctrl R
3182 StartRoute();
3183 return;
3184
3185 case 20: // Ctrl T
3186 if (NULL == pGoToPositionDialog) // There is one global instance of
3187 // the Go To Position Dialog
3188 pGoToPositionDialog = new GoToPositionDialog(this);
3189 pGoToPositionDialog->SetCanvas(this);
3190 pGoToPositionDialog->Show();
3191 break;
3192
3193 case 25: // Ctrl Y
3194 if (undo->AnythingToRedo()) {
3195 undo->RedoNextAction();
3196 InvalidateGL();
3197 Refresh(false);
3198 }
3199 break;
3200
3201 case 26:
3202 if (event.ShiftDown()) { // Shift-Ctrl-Z
3203 if (undo->AnythingToRedo()) {
3204 undo->RedoNextAction();
3205 InvalidateGL();
3206 Refresh(false);
3207 }
3208 } else { // Ctrl Z
3209 if (undo->AnythingToUndo()) {
3210 undo->UndoLastAction();
3211 InvalidateGL();
3212 Refresh(false);
3213 }
3214 }
3215 break;
3216
3217 case 27:
3218 // Generic break
3219 if (m_bMeasure_Active) {
3220 CancelMeasureRoute();
3221
3222 SetCursor(*pCursorArrow);
3223
3224 // SurfaceToolbar();
3225 gFrame->RefreshAllCanvas();
3226 }
3227
3228 if (m_routeState) // creating route?
3229 {
3230 FinishRoute();
3231 // SurfaceToolbar();
3232 InvalidateGL();
3233 Refresh(false);
3234 }
3235
3236 break;
3237
3238 case 7: // Ctrl G
3239 switch (gamma_state) {
3240 case (0):
3241 r_gamma_mult = 0;
3242 g_gamma_mult = 1;
3243 b_gamma_mult = 0;
3244 gamma_state = 1;
3245 break;
3246 case (1):
3247 r_gamma_mult = 1;
3248 g_gamma_mult = 0;
3249 b_gamma_mult = 0;
3250 gamma_state = 2;
3251 break;
3252 case (2):
3253 r_gamma_mult = 1;
3254 g_gamma_mult = 1;
3255 b_gamma_mult = 1;
3256 gamma_state = 0;
3257 break;
3258 }
3259 SetScreenBrightness(g_nbrightness);
3260
3261 break;
3262
3263 case 9: // Ctrl I
3264 if (event.ControlDown()) {
3265 m_bShowCompassWin = !m_bShowCompassWin;
3266 SetShowGPSCompassWindow(m_bShowCompassWin);
3267 Refresh(false);
3268 }
3269 break;
3270
3271 default:
3272 break;
3273
3274 } // switch
3275 }
3276
3277 // Allow OnKeyChar to catch the key events too.
3278 if (!b_handled) {
3279 event.Skip();
3280 }
3281}
3282
3283void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3284 if (SendKeyEventToPlugins(event))
3285 return; // PlugIn did something, and does not want the canvas to do
3286 // anything else
3287
3288 switch (event.GetKeyCode()) {
3289 case WXK_TAB:
3290 parent_frame->SwitchKBFocus(this);
3291 break;
3292
3293 case WXK_LEFT:
3294 case WXK_RIGHT:
3295 m_panx = 0;
3296 if (!m_pany) m_panspeed = 0;
3297 break;
3298
3299 case WXK_UP:
3300 case WXK_DOWN:
3301 m_pany = 0;
3302 if (!m_panx) m_panspeed = 0;
3303 break;
3304
3305 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3306 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3307 case WXK_PAGEUP:
3308 case WXK_PAGEDOWN:
3309 if (m_mustmove) DoMovement(m_mustmove);
3310
3311 m_zoom_factor = 1;
3312 break;
3313
3314 case WXK_ALT:
3315 m_modkeys &= ~wxMOD_ALT;
3316#ifdef OCPN_ALT_MENUBAR
3317#ifndef __WXOSX__
3318 // If the permanent menu bar is disabled, and we are not in the middle of
3319 // another key combo, then show the menu bar temporarily when Alt is
3320 // released (or hide it if already visible).
3321 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3322 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3323 parent_frame->ApplyGlobalSettings(false);
3324 }
3325 m_bMayToggleMenuBar = true;
3326#endif
3327#endif
3328 break;
3329
3330 case WXK_CONTROL:
3331 m_modkeys &= ~wxMOD_CONTROL;
3332 break;
3333 }
3334
3335 if (event.GetKeyCode() < 128) // ascii
3336 {
3337 int key_char = event.GetKeyCode();
3338
3339 // Handle both QWERTY and AZERTY keyboard separately for a few control
3340 // codes
3341 if (!g_b_assume_azerty) {
3342 switch (key_char) {
3343 case '+':
3344 case '=':
3345 case '-':
3346 case '_':
3347 case 54:
3348 case 56: // '_' alpha/num pad
3349 DoMovement(m_mustmove);
3350
3351 // m_zoom_factor = 1;
3352 break;
3353 case '[':
3354 case ']':
3355 DoMovement(m_mustmove);
3356 m_rotation_speed = 0;
3357 break;
3358 }
3359 } else {
3360 switch (key_char) {
3361 case 43:
3362 case 54: // '-' alpha/num pad
3363 case 56: // '_' alpha/num pad
3364 DoMovement(m_mustmove);
3365
3366 m_zoom_factor = 1;
3367 break;
3368 }
3369 }
3370 }
3371 event.Skip();
3372}
3373
3374void ChartCanvas::ToggleChartOutlines(void) {
3375 m_bShowOutlines = !m_bShowOutlines;
3376
3377 Refresh(false);
3378
3379#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3380 // needs a full refresh
3381 if (g_bopengl) InvalidateGL();
3382#endif
3383}
3384
3385void ChartCanvas::ToggleLookahead() {
3386 m_bLookAhead = !m_bLookAhead;
3387 m_OSoffsetx = 0; // center ownship
3388 m_OSoffsety = 0;
3389}
3390
3391void ChartCanvas::SetUpMode(int mode) {
3392 m_upMode = mode;
3393
3394 if (mode != NORTH_UP_MODE) {
3395 // Stuff the COGAvg table in case COGUp is selected
3396 double stuff = 0;
3397 if (!std::isnan(gCog)) stuff = gCog;
3398
3399 if (g_COGAvgSec > 0) {
3400 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3401 }
3402 g_COGAvg = stuff;
3403 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3404 } else {
3405 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3406 SetVPRotation(GetVPSkew());
3407 else
3408 SetVPRotation(0); /* reset to north up */
3409 }
3410
3411 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3412 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3413
3414 UpdateGPSCompassStatusBox(true);
3415 gFrame->DoChartUpdate();
3416}
3417
3418bool ChartCanvas::DoCanvasCOGSet(void) {
3419 if (GetUpMode() == NORTH_UP_MODE) return false;
3420 double cog_use = g_COGAvg;
3421 if (g_btenhertz) cog_use = gCog;
3422
3423 double rotation = 0;
3424 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3425 rotation = -gHdt * PI / 180.;
3426 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3427 rotation = -cog_use * PI / 180.;
3428
3429 SetVPRotation(rotation);
3430 return true;
3431}
3432
3433double easeOutCubic(double t) {
3434 // Starts quickly and slows down toward the end
3435 return 1.0 - pow(1.0 - t, 3.0);
3436}
3437
3438void ChartCanvas::StartChartDragInertia() {
3439 //
3440 // printf("\nStart ChartDragInertia\n");
3441 m_bChartDragging = false;
3442
3443 // Set some parameters
3444 m_chart_drag_inertia_time = 750; // msec
3445 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3446 m_last_elapsed = 0;
3447
3448 // Calculate ending drag velocity
3449 size_t n_vel = 10;
3450 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3451 int xacc = 0;
3452 int yacc = 0;
3453 double tacc = 0;
3454 size_t length = m_drag_vec_t.size();
3455 for (size_t i = 0; i < n_vel; i++) {
3456 xacc += m_drag_vec_x.at(length - 1 - i);
3457 yacc += m_drag_vec_y.at(length - 1 - i);
3458 tacc += m_drag_vec_t.at(length - 1 - i);
3459 // printf("%d %g\n", xacc, tacc);
3460 }
3461 m_chart_drag_velocity_x = xacc / tacc;
3462 m_chart_drag_velocity_y = yacc / tacc;
3463
3464 m_chart_drag_inertia_active = true;
3465
3466 // First callback as fast as possible.
3467 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3468
3469 // printf(" Drag parms %d %d %g\n", m_chart_drag_total_x,
3470 // m_chart_drag_total_y, m_chart_drag_total_time);
3471}
3472
3473void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3474 if (!m_chart_drag_inertia_active) return;
3475
3476 // Calculate time fraction from 0..1
3477 wxLongLong now = wxGetLocalTimeMillis();
3478 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3479 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3480 if (t > 1.0) t = 1.0;
3481 double e = 1.0 - easeOutCubic(t); // 0..1
3482
3483 double dx =
3484 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3485 double dy =
3486 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3487
3488 // double distance = pow((pow(dx, 2) + pow(dy, 2)), 0.5);
3489 // printf(" %5g %5g %5g %5g pix/sec\n", elapsed,
3490 // elapsed - m_last_elapsed, distance, distance * 1000 / elapsed);
3491
3492 m_last_elapsed = elapsed;
3493
3494 // Ensure that target destination lies on whole-pixel boundary
3495 // This allows the render engine to use a faster FBO copy method for drawing
3496 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3497 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3498 double inertia_lat, inertia_lon;
3499 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3500 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3501
3502 Refresh(false);
3503
3504 // Stop condition
3505 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3506 m_chart_drag_inertia_timer.Stop();
3507 m_chart_drag_inertia_active = false;
3508
3509 // Disable chart pan movement logic
3510 m_target_lat = GetVP().clat;
3511 m_target_lon = GetVP().clon;
3512 m_pan_drag.x = m_pan_drag.y = 0;
3513 m_panx = m_pany = 0;
3514
3515 } else {
3516 int target_redraw_interval = 40; // msec
3517 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3518 }
3519}
3520
3521void ChartCanvas::StopMovement() {
3522 m_panx = m_pany = 0;
3523 m_panspeed = 0;
3524 m_zoom_factor = 1;
3525 m_rotation_speed = 0;
3526 m_mustmove = 0;
3527#if 0
3528#if !defined(__WXGTK__) && !defined(__WXQT__)
3529 SetFocus();
3530 gFrame->Raise();
3531#endif
3532#endif
3533}
3534
3535/* instead of integrating in timer callbacks
3536 (which do not always get called fast enough)
3537 we can perform the integration of movement
3538 at each render frame based on the time change */
3539bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3540 // Start/restart the stop movement timer
3541 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3542
3543 if (!pMovementTimer->IsRunning()) {
3544 // printf("timer not running, starting\n");
3545 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3546 }
3547
3548 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3549 // already moving, gets called again because of key-repeat event
3550 return false;
3551 }
3552
3553 m_last_movement_time = wxDateTime::UNow();
3554
3555 return true;
3556}
3557void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3558 int nstep) {
3559 // Save the target
3560 m_target_lat = target_lat;
3561 m_target_lon = target_lon;
3562
3563 // Save the start point
3564 m_start_lat = GetVP().clat;
3565 m_start_lon = GetVP().clon;
3566
3567 m_VPMovementTimer.Start(1, true); // oneshot
3568 m_timed_move_vp_active = true;
3569 m_stvpc = 0;
3570 m_timedVP_step = nstep;
3571}
3572
3573void ChartCanvas::DoTimedMovementVP() {
3574 if (!m_timed_move_vp_active) return; // not active
3575 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3576 StopMovement();
3577 return;
3578 }
3579 // Stop condition
3580 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3581 double d2 =
3582 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3583 d2 = pow(d2, 0.5);
3584
3585 if (d2 < one_pix) {
3586 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3587 StopMovementVP();
3588 return;
3589 }
3590
3591 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3592 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3593 // StopMovementVP();
3594 // return;
3595 // }
3596
3597 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3598 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3599
3600 m_run_lat = new_lat;
3601 m_run_lon = new_lon;
3602
3603 // printf(" Timed\n");
3604 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3605}
3606
3607void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3608
3609void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3610
3611void ChartCanvas::StartTimedMovementTarget() {}
3612
3613void ChartCanvas::DoTimedMovementTarget() {}
3614
3615void ChartCanvas::StopMovementTarget() {}
3616
3617void ChartCanvas::DoTimedMovement() {
3618 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3619 !m_rotation_speed)
3620 return; /* not moving */
3621
3622 wxDateTime now = wxDateTime::UNow();
3623 long dt = 0;
3624 if (m_last_movement_time.IsValid())
3625 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3626
3627 m_last_movement_time = now;
3628
3629 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3630 dt = 500;
3631
3632 DoMovement(dt);
3633}
3634
3635void ChartCanvas::DoMovement(long dt) {
3636 /* if we get here quickly assume 1ms so that some movement occurs */
3637 if (dt == 0) dt = 1;
3638
3639 m_mustmove -= dt;
3640 if (m_mustmove < 0) m_mustmove = 0;
3641
3642 if (m_pan_drag.x || m_pan_drag.y) {
3643 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3644 m_pan_drag.x = m_pan_drag.y = 0;
3645 }
3646
3647 if (m_panx || m_pany) {
3648 const double slowpan = .1, maxpan = 2;
3649 if (m_modkeys == wxMOD_ALT)
3650 m_panspeed = slowpan;
3651 else {
3652 m_panspeed += (double)dt / 500; /* apply acceleration */
3653 m_panspeed = wxMin(maxpan, m_panspeed);
3654 }
3655 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3656 }
3657
3658 if (m_zoom_factor != 1) {
3659 double alpha = 400, beta = 1.5;
3660 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3661
3662 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3663
3664 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3665
3666 // Try to hit the zoom target exactly.
3667 // if(m_wheelzoom_stop_oneshot > 0)
3668 {
3669 if (zoom_factor > 1) {
3670 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3671 zoom_factor = VPoint.chart_scale / m_zoom_target;
3672 }
3673
3674 else if (zoom_factor < 1) {
3675 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3676 zoom_factor = VPoint.chart_scale / m_zoom_target;
3677 }
3678 }
3679
3680 if (fabs(zoom_factor - 1) > 1e-4)
3681 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3682
3683 if (m_wheelzoom_stop_oneshot > 0) {
3684 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3685 m_wheelzoom_stop_oneshot = 0;
3686 StopMovement();
3687 }
3688
3689 // Don't overshoot the zoom target.
3690 if (zoom_factor > 1) {
3691 if (VPoint.chart_scale <= m_zoom_target) {
3692 m_wheelzoom_stop_oneshot = 0;
3693 StopMovement();
3694 }
3695 } else if (zoom_factor < 1) {
3696 if (VPoint.chart_scale >= m_zoom_target) {
3697 m_wheelzoom_stop_oneshot = 0;
3698 StopMovement();
3699 }
3700 }
3701 }
3702 }
3703
3704 if (m_rotation_speed) { /* in degrees per second */
3705 double speed = m_rotation_speed;
3706 if (m_modkeys == wxMOD_ALT) speed /= 10;
3707 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3708 }
3709}
3710
3711void ChartCanvas::SetColorScheme(ColorScheme cs) {
3712 SetAlertString(_T(""));
3713
3714 // Setup ownship image pointers
3715 switch (cs) {
3716 case GLOBAL_COLOR_SCHEME_DAY:
3717 m_pos_image_red = &m_os_image_red_day;
3718 m_pos_image_grey = &m_os_image_grey_day;
3719 m_pos_image_yellow = &m_os_image_yellow_day;
3720 m_pos_image_user = m_pos_image_user_day;
3721 m_pos_image_user_grey = m_pos_image_user_grey_day;
3722 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3723 m_cTideBitmap = m_bmTideDay;
3724 m_cCurrentBitmap = m_bmCurrentDay;
3725
3726 break;
3727 case GLOBAL_COLOR_SCHEME_DUSK:
3728 m_pos_image_red = &m_os_image_red_dusk;
3729 m_pos_image_grey = &m_os_image_grey_dusk;
3730 m_pos_image_yellow = &m_os_image_yellow_dusk;
3731 m_pos_image_user = m_pos_image_user_dusk;
3732 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3733 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3734 m_cTideBitmap = m_bmTideDusk;
3735 m_cCurrentBitmap = m_bmCurrentDusk;
3736 break;
3737 case GLOBAL_COLOR_SCHEME_NIGHT:
3738 m_pos_image_red = &m_os_image_red_night;
3739 m_pos_image_grey = &m_os_image_grey_night;
3740 m_pos_image_yellow = &m_os_image_yellow_night;
3741 m_pos_image_user = m_pos_image_user_night;
3742 m_pos_image_user_grey = m_pos_image_user_grey_night;
3743 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3744 m_cTideBitmap = m_bmTideNight;
3745 m_cCurrentBitmap = m_bmCurrentNight;
3746 break;
3747 default:
3748 m_pos_image_red = &m_os_image_red_day;
3749 m_pos_image_grey = &m_os_image_grey_day;
3750 m_pos_image_yellow = &m_os_image_yellow_day;
3751 m_pos_image_user = m_pos_image_user_day;
3752 m_pos_image_user_grey = m_pos_image_user_grey_day;
3753 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3754 m_cTideBitmap = m_bmTideDay;
3755 m_cCurrentBitmap = m_bmCurrentDay;
3756 break;
3757 }
3758
3759 CreateDepthUnitEmbossMaps(cs);
3760 CreateOZEmbossMapData(cs);
3761
3762 // Set up fog effect base color
3763 m_fog_color = wxColor(
3764 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3765 float dim = 1.0;
3766 switch (cs) {
3767 case GLOBAL_COLOR_SCHEME_DUSK:
3768 dim = 0.5;
3769 break;
3770 case GLOBAL_COLOR_SCHEME_NIGHT:
3771 dim = 0.25;
3772 break;
3773 default:
3774 break;
3775 }
3776 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3777 m_fog_color.Blue() * dim);
3778
3779 // Really dark
3780#if 0
3781 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3782 SetBackgroundColour( wxColour(0,0,0) );
3783
3784 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3785 }
3786 else{
3787 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3788#ifndef __WXMAC__
3789 SetBackgroundColour( wxNullColour );
3790#endif
3791 }
3792#endif
3793
3794 // UpdateToolbarColorScheme(cs);
3795
3796 m_Piano->SetColorScheme(cs);
3797
3798 m_Compass->SetColorScheme(cs);
3799
3800 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3801
3802 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3803#ifdef ocpnUSE_GL
3804 if (g_bopengl && m_glcc) {
3805 m_glcc->SetColorScheme(cs);
3806 g_glTextureManager->ClearAllRasterTextures();
3807 // m_glcc->FlushFBO();
3808 }
3809#endif
3810 SetbTCUpdate(true); // force re-render of tide/current locators
3811 m_brepaint_piano = true;
3812
3813 ReloadVP();
3814
3815 m_cs = cs;
3816}
3817
3818wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3819 wxImage img = Bitmap.ConvertToImage();
3820 int sx = img.GetWidth();
3821 int sy = img.GetHeight();
3822
3823 wxImage new_img(img);
3824
3825 for (int i = 0; i < sx; i++) {
3826 for (int j = 0; j < sy; j++) {
3827 if (!img.IsTransparent(i, j)) {
3828 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3829 (unsigned char)(img.GetGreen(i, j) * factor),
3830 (unsigned char)(img.GetBlue(i, j) * factor));
3831 }
3832 }
3833 }
3834
3835 wxBitmap ret = wxBitmap(new_img);
3836
3837 return ret;
3838}
3839
3840void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3841 int max) {
3842 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3843 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3844
3845 if (!m_pBrightPopup) {
3846 // Calculate size
3847 int x, y;
3848 GetTextExtent(_T("MAX"), &x, &y, NULL, NULL, pfont);
3849
3850 m_pBrightPopup = new TimedPopupWin(this, 3);
3851
3852 m_pBrightPopup->SetSize(x, y);
3853 m_pBrightPopup->Move(120, 120);
3854 }
3855
3856 int bmpsx = m_pBrightPopup->GetSize().x;
3857 int bmpsy = m_pBrightPopup->GetSize().y;
3858
3859 wxBitmap bmp(bmpsx, bmpsx);
3860 wxMemoryDC mdc(bmp);
3861
3862 mdc.SetTextForeground(GetGlobalColor(_T("GREEN4")));
3863 mdc.SetBackground(wxBrush(GetGlobalColor(_T("UINFD"))));
3864 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3865 mdc.SetBrush(wxBrush(GetGlobalColor(_T("UINFD"))));
3866 mdc.Clear();
3867
3868 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3869
3870 mdc.SetFont(*pfont);
3871 wxString val;
3872
3873 if (brightness == max)
3874 val = _T("MAX");
3875 else if (brightness == min)
3876 val = _T("MIN");
3877 else
3878 val.Printf(_T("%3d"), brightness);
3879
3880 mdc.DrawText(val, 0, 0);
3881
3882 mdc.SelectObject(wxNullBitmap);
3883
3884 m_pBrightPopup->SetBitmap(bmp);
3885 m_pBrightPopup->Show();
3886 m_pBrightPopup->Refresh();
3887}
3888
3889void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3890 m_b_rot_hidef = true;
3891 ReloadVP();
3892}
3893
3894void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3895 if (!g_bRollover) return;
3896
3897 bool b_need_refresh = false;
3898
3899 wxSize win_size = GetSize() * m_displayScale;
3900 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3901
3902 // Handle the AIS Rollover Window first
3903 bool showAISRollover = false;
3904 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3905 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3906 SelectItem *pFind = pSelectAIS->FindSelection(
3907 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3908 if (pFind) {
3909 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3910 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3911
3912 if (ptarget) {
3913 showAISRollover = true;
3914
3915 if (NULL == m_pAISRolloverWin) {
3916 m_pAISRolloverWin = new RolloverWin(this);
3917 m_pAISRolloverWin->IsActive(false);
3918 b_need_refresh = true;
3919 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3920 m_AISRollover_MMSI != FoundAIS_MMSI) {
3921 // Sometimes the mouse moves fast enough to get over a new AIS
3922 // target before the one-shot has fired to remove the old target.
3923 // Result: wrong target data is shown.
3924 // Detect this case,close the existing rollover ASAP, and restart
3925 // the timer.
3926 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3927 m_pAISRolloverWin->IsActive(false);
3928 m_AISRollover_MMSI = 0;
3929 Refresh();
3930 return;
3931 }
3932
3933 m_AISRollover_MMSI = FoundAIS_MMSI;
3934
3935 if (!m_pAISRolloverWin->IsActive()) {
3936 wxString s = ptarget->GetRolloverString();
3937 m_pAISRolloverWin->SetString(s);
3938
3939 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3940 AIS_ROLLOVER, win_size);
3941 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3942 m_pAISRolloverWin->IsActive(true);
3943 b_need_refresh = true;
3944 }
3945 }
3946 } else {
3947 m_AISRollover_MMSI = 0;
3948 showAISRollover = false;
3949 }
3950 }
3951
3952 // Maybe turn the rollover off
3953 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3954 m_pAISRolloverWin->IsActive(false);
3955 m_AISRollover_MMSI = 0;
3956 b_need_refresh = true;
3957 }
3958
3959 // Now the Route info rollover
3960 // Show the route segment info
3961 bool showRouteRollover = false;
3962
3963 if (NULL == m_pRolloverRouteSeg) {
3964 // Get a list of all selectable sgements, and search for the first
3965 // visible segment as the rollover target.
3966
3967 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3968 SelectableItemList SelList = pSelect->FindSelectionList(
3969 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3970 wxSelectableItemListNode *node = SelList.GetFirst();
3971 while (node) {
3972 SelectItem *pFindSel = node->GetData();
3973
3974 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3975
3976 if (pr && pr->IsVisible()) {
3977 m_pRolloverRouteSeg = pFindSel;
3978 showRouteRollover = true;
3979
3980 if (NULL == m_pRouteRolloverWin) {
3981 m_pRouteRolloverWin = new RolloverWin(this, 10);
3982 m_pRouteRolloverWin->IsActive(false);
3983 }
3984
3985 if (!m_pRouteRolloverWin->IsActive()) {
3986 wxString s;
3987 RoutePoint *segShow_point_a =
3988 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
3989 RoutePoint *segShow_point_b =
3990 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
3991
3992 double brg, dist;
3993 DistanceBearingMercator(
3994 segShow_point_b->m_lat, segShow_point_b->m_lon,
3995 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
3996
3997 if (!pr->m_bIsInLayer)
3998 s.Append(_("Route") + _T(": "));
3999 else
4000 s.Append(_("Layer Route: "));
4001
4002 if (pr->m_RouteNameString.IsEmpty())
4003 s.Append(_("(unnamed)"));
4004 else
4005 s.Append(pr->m_RouteNameString);
4006
4007 s << _T("\n") << _("Total Length: ")
4008 << FormatDistanceAdaptive(pr->m_route_length) << _T("\n")
4009 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4010 << segShow_point_b->GetName() << _T("\n");
4011
4012 if (g_bShowTrue)
4013 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4014 (int)floor(brg + 0.5), 0x00B0);
4015 if (g_bShowMag) {
4016 double latAverage =
4017 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4018 double lonAverage =
4019 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4020 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4021
4022 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4023 (int)floor(varBrg + 0.5), 0x00B0);
4024 }
4025
4026 s << FormatDistanceAdaptive(dist);
4027
4028 // Compute and display cumulative distance from route start point to
4029 // current leg end point and RNG,TTG,ETA from ship to current leg end
4030 // point for active route
4031 double shiptoEndLeg = 0.;
4032 bool validActive = false;
4033 if (pr->IsActive() &&
4034 pr->pRoutePointList->GetFirst()->GetData()->m_bIsActive)
4035 validActive = true;
4036
4037 if (segShow_point_a != pr->pRoutePointList->GetFirst()->GetData()) {
4038 wxRoutePointListNode *node =
4039 (pr->pRoutePointList)->GetFirst()->GetNext();
4040 RoutePoint *prp;
4041 float dist_to_endleg = 0;
4042 wxString t;
4043
4044 while (node) {
4045 prp = node->GetData();
4046 if (validActive)
4047 shiptoEndLeg += prp->m_seg_len;
4048 else if (prp->m_bIsActive)
4049 validActive = true;
4050 dist_to_endleg += prp->m_seg_len;
4051 if (prp->IsSame(segShow_point_a)) break;
4052 node = node->GetNext();
4053 }
4054 s << _T(" (+") << FormatDistanceAdaptive(dist_to_endleg) << _T(")");
4055 }
4056 // write from ship to end selected leg point data if the route is
4057 // active
4058 if (validActive) {
4059 s << _T("\n") << _("From Ship To") << _T(" ")
4060 << segShow_point_b->GetName() << _T("\n");
4061 shiptoEndLeg +=
4062 g_pRouteMan
4063 ->GetCurrentRngToActivePoint(); // add distance from ship
4064 // to active point
4065 shiptoEndLeg +=
4066 segShow_point_b
4067 ->m_seg_len; // add the lenght of the selected leg
4068 s << FormatDistanceAdaptive(shiptoEndLeg);
4069 // ensure sog/cog are valid and vmg is positive to keep data
4070 // coherent
4071 double vmg = 0.;
4072 if (!std::isnan(gCog) && !std::isnan(gSog))
4073 vmg = gSog *
4074 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4075 PI / 180.);
4076 if (vmg > 0.) {
4077 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4078 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4079 s << _T(" - ")
4080 << wxString(ttg_sec > SECONDS_PER_DAY
4081 ? ttg_span.Format(_("%Dd %H:%M"))
4082 : ttg_span.Format(_("%H:%M")));
4083 wxDateTime dtnow, eta;
4084 eta = dtnow.SetToCurrent().Add(ttg_span);
4085 s << _T(" - ") << eta.Format(_T("%b")).Mid(0, 4)
4086 << eta.Format(_T(" %d %H:%M"));
4087 } else
4088 s << _T(" ---- ----");
4089 }
4090 m_pRouteRolloverWin->SetString(s);
4091
4092 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4093 LEG_ROLLOVER, win_size);
4094 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4095 m_pRouteRolloverWin->IsActive(true);
4096 b_need_refresh = true;
4097 showRouteRollover = true;
4098 break;
4099 }
4100 } else
4101 node = node->GetNext();
4102 }
4103 } else {
4104 // Is the cursor still in select radius, and not timed out?
4105 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4106 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4107 m_pRolloverRouteSeg))
4108 showRouteRollover = false;
4109 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4110 showRouteRollover = false;
4111 else
4112 showRouteRollover = true;
4113 }
4114
4115 // If currently creating a route, do not show this rollover window
4116 if (m_routeState) showRouteRollover = false;
4117
4118 // Similar for AIS target rollover window
4119 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4120 showRouteRollover = false;
4121
4122 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4123 !showRouteRollover) {
4124 m_pRouteRolloverWin->IsActive(false);
4125 m_pRolloverRouteSeg = NULL;
4126 m_pRouteRolloverWin->Destroy();
4127 m_pRouteRolloverWin = NULL;
4128 b_need_refresh = true;
4129 } else if (m_pRouteRolloverWin && showRouteRollover) {
4130 m_pRouteRolloverWin->IsActive(true);
4131 b_need_refresh = true;
4132 }
4133
4134 // Now the Track info rollover
4135 // Show the track segment info
4136 bool showTrackRollover = false;
4137
4138 if (NULL == m_pRolloverTrackSeg) {
4139 // Get a list of all selectable sgements, and search for the first
4140 // visible segment as the rollover target.
4141
4142 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4143 SelectableItemList SelList = pSelect->FindSelectionList(
4144 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4145 wxSelectableItemListNode *node = SelList.GetFirst();
4146 while (node) {
4147 SelectItem *pFindSel = node->GetData();
4148
4149 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4150
4151 if (pt && pt->IsVisible()) {
4152 m_pRolloverTrackSeg = pFindSel;
4153 showTrackRollover = true;
4154
4155 if (NULL == m_pTrackRolloverWin) {
4156 m_pTrackRolloverWin = new RolloverWin(this, 10);
4157 m_pTrackRolloverWin->IsActive(false);
4158 }
4159
4160 if (!m_pTrackRolloverWin->IsActive()) {
4161 wxString s;
4162 TrackPoint *segShow_point_a =
4163 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4164 TrackPoint *segShow_point_b =
4165 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4166
4167 double brg, dist;
4168 DistanceBearingMercator(
4169 segShow_point_b->m_lat, segShow_point_b->m_lon,
4170 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4171
4172 if (!pt->m_bIsInLayer)
4173 s.Append(_("Track") + _T(": "));
4174 else
4175 s.Append(_("Layer Track: "));
4176
4177 if (pt->GetName().IsEmpty())
4178 s.Append(_("(unnamed)"));
4179 else
4180 s.Append(pt->GetName());
4181 double tlenght = pt->Length();
4182 s << _T("\n") << _("Total Track: ")
4183 << FormatDistanceAdaptive(tlenght);
4184 if (pt->GetLastPoint()->GetTimeString() &&
4185 pt->GetPoint(0)->GetTimeString()) {
4186 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4187 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4188 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4189 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4190 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4191 s << wxString::Format(_T(" %.1f "), (float)(tlenght / htime))
4192 << getUsrSpeedUnit();
4193 s << wxString(htime > 24. ? ttime.Format(_T(" %Dd %H:%M"))
4194 : ttime.Format(_T(" %H:%M")));
4195 }
4196 }
4197
4198 if (g_bShowTrackPointTime && strlen(segShow_point_b->GetTimeString()))
4199 s << _T("\n") << _("Segment Created: ")
4200 << segShow_point_b->GetTimeString();
4201
4202 s << _T("\n");
4203 if (g_bShowTrue)
4204 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4205 0x00B0);
4206
4207 if (g_bShowMag) {
4208 double latAverage =
4209 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4210 double lonAverage =
4211 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4212 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4213
4214 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4215 0x00B0);
4216 }
4217
4218 s << FormatDistanceAdaptive(dist);
4219
4220 if (segShow_point_a->GetTimeString() &&
4221 segShow_point_b->GetTimeString()) {
4222 wxDateTime apoint = segShow_point_a->GetCreateTime();
4223 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4224 if (apoint.IsValid() && bpoint.IsValid()) {
4225 double segmentSpeed = toUsrSpeed(
4226 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4227 s << wxString::Format(_T(" %.1f "), (float)segmentSpeed)
4228 << getUsrSpeedUnit();
4229 }
4230 }
4231
4232 m_pTrackRolloverWin->SetString(s);
4233
4234 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4235 LEG_ROLLOVER, win_size);
4236 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4237 m_pTrackRolloverWin->IsActive(true);
4238 b_need_refresh = true;
4239 showTrackRollover = true;
4240 break;
4241 }
4242 } else
4243 node = node->GetNext();
4244 }
4245 } else {
4246 // Is the cursor still in select radius, and not timed out?
4247 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4248 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4249 m_pRolloverTrackSeg))
4250 showTrackRollover = false;
4251 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4252 showTrackRollover = false;
4253 else
4254 showTrackRollover = true;
4255 }
4256
4257 // Similar for AIS target rollover window
4258 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4259 showTrackRollover = false;
4260
4261 // Similar for route rollover window
4262 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4263 showTrackRollover = false;
4264
4265 // TODO We onlt show tracks on primary canvas....
4266 // if(!IsPrimaryCanvas())
4267 // showTrackRollover = false;
4268
4269 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4270 !showTrackRollover) {
4271 m_pTrackRolloverWin->IsActive(false);
4272 m_pRolloverTrackSeg = NULL;
4273 m_pTrackRolloverWin->Destroy();
4274 m_pTrackRolloverWin = NULL;
4275 b_need_refresh = true;
4276 } else if (m_pTrackRolloverWin && showTrackRollover) {
4277 m_pTrackRolloverWin->IsActive(true);
4278 b_need_refresh = true;
4279 }
4280
4281 if (b_need_refresh) Refresh();
4282}
4283
4284void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4285 if ((GetShowENCLights() || m_bsectors_shown) &&
4286 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4287 extendedSectorLegs)) {
4288 if (!m_bsectors_shown) {
4289 ReloadVP(false);
4290 m_bsectors_shown = true;
4291 }
4292 } else {
4293 if (m_bsectors_shown) {
4294 ReloadVP(false);
4295 m_bsectors_shown = false;
4296 }
4297 }
4298
4299// This is here because GTK status window update is expensive..
4300// cairo using pango rebuilds the font every time so is very
4301// inefficient
4302// Anyway, only update the status bar when this timer expires
4303#if defined(__WXGTK__) || defined(__WXQT__)
4304 {
4305 // Check the absolute range of the cursor position
4306 // There could be a window wherein the chart geoereferencing is not
4307 // valid....
4308 double cursor_lat, cursor_lon;
4309 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4310
4311 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4312 while (cursor_lon < -180.) cursor_lon += 360.;
4313
4314 while (cursor_lon > 180.) cursor_lon -= 360.;
4315
4316 SetCursorStatus(cursor_lat, cursor_lon);
4317 }
4318 }
4319#endif
4320}
4321
4322void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4323 if (!parent_frame->m_pStatusBar) return;
4324
4325 wxString s1;
4326 s1 += _T(" ");
4327 s1 += toSDMM(1, cursor_lat);
4328 s1 += _T(" ");
4329 s1 += toSDMM(2, cursor_lon);
4330
4331 if (STAT_FIELD_CURSOR_LL >= 0)
4332 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4333
4334 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4335
4336 double brg, dist;
4337 wxString sm;
4338 wxString st;
4339 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4340 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4341 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4342
4343 wxString s = st + sm;
4344 s << FormatDistanceAdaptive(dist);
4345
4346 // CUSTOMIZATION - LIVE ETA OPTION
4347 // -------------------------------------------------------
4348 // Calculate an "live" ETA based on route starting from the current
4349 // position of the boat and goes to the cursor of the mouse.
4350 // In any case, an standard ETA will be calculated with a default speed
4351 // of the boat to give an estimation of the route (in particular if GPS
4352 // is off).
4353
4354 // Display only if option "live ETA" is selected in Settings > Display >
4355 // General.
4356 if (g_bShowLiveETA) {
4357 float realTimeETA;
4358 float boatSpeed;
4359 float boatSpeedDefault = g_defaultBoatSpeed;
4360
4361 // Calculate Estimate Time to Arrival (ETA) in minutes
4362 // Check before is value not closed to zero (it will make an very big
4363 // number...)
4364 if (!std::isnan(gSog)) {
4365 boatSpeed = gSog;
4366 if (boatSpeed < 0.5) {
4367 realTimeETA = 0;
4368 } else {
4369 realTimeETA = dist / boatSpeed * 60;
4370 }
4371 } else {
4372 realTimeETA = 0;
4373 }
4374
4375 // Add space after distance display
4376 s << " ";
4377 // Display ETA
4378 s << minutesToHoursDays(realTimeETA);
4379
4380 // In any case, display also an ETA with default speed at 6knts
4381
4382 s << " [@";
4383 s << wxString::Format(_T("%d"), (int)toUsrSpeed(boatSpeedDefault, -1));
4384 s << wxString::Format(_T("%s"), getUsrSpeedUnit(-1));
4385 s << " ";
4386 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4387 s << "]";
4388 }
4389 // END OF - LIVE ETA OPTION
4390
4391 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4392}
4393
4394// CUSTOMIZATION - FORMAT MINUTES
4395// -------------------------------------------------------
4396// New function to format minutes into a more readable format:
4397// * Hours + minutes, or
4398// * Days + hours.
4399wxString minutesToHoursDays(float timeInMinutes) {
4400 wxString s;
4401
4402 if (timeInMinutes == 0) {
4403 s << "--min";
4404 }
4405
4406 // Less than 60min, keep time in minutes
4407 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4408 s << wxString::Format(_T("%d"), (int)timeInMinutes);
4409 s << "min";
4410 }
4411
4412 // Between 1h and less than 24h, display time in hours, minutes
4413 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4414 int hours;
4415 int min;
4416 hours = (int)timeInMinutes / 60;
4417 min = (int)timeInMinutes % 60;
4418
4419 if (min == 0) {
4420 s << wxString::Format(_T("%d"), hours);
4421 s << "h";
4422 } else {
4423 s << wxString::Format(_T("%d"), hours);
4424 s << "h";
4425 s << wxString::Format(_T("%d"), min);
4426 s << "min";
4427 }
4428
4429 }
4430
4431 // More than 24h, display time in days, hours
4432 else if (timeInMinutes > 24 * 60) {
4433 int days;
4434 int hours;
4435 days = (int)(timeInMinutes / 60) / 24;
4436 hours = (int)(timeInMinutes / 60) % 24;
4437
4438 if (hours == 0) {
4439 s << wxString::Format(_T("%d"), days);
4440 s << "d";
4441 } else {
4442 s << wxString::Format(_T("%d"), days);
4443 s << "d";
4444 s << wxString::Format(_T("%d"), hours);
4445 s << "h";
4446 }
4447 }
4448
4449 return s;
4450}
4451
4452// END OF CUSTOMIZATION - FORMAT MINUTES
4453// Thanks open source code ;-)
4454// -------------------------------------------------------
4455
4456void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4457 double clat, clon;
4458 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4459 *lat = clat;
4460 *lon = clon;
4461}
4462
4463void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4464 wxPoint2DDouble *r) {
4465 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4466}
4467
4469 double rlon, wxPoint2DDouble *r) {
4470 // If the Current Chart is a raster chart, and the
4471 // requested lat/long is within the boundaries of the chart,
4472 // and the VP is not rotated,
4473 // then use the embedded BSB chart georeferencing algorithm
4474 // for greater accuracy
4475 // Additionally, use chart embedded georef if the projection is TMERC
4476 // i.e. NOT MERCATOR and NOT POLYCONIC
4477
4478 // If for some reason the chart rejects the request by returning an error,
4479 // then fall back to Viewport Projection estimate from canvas parameters
4480 if (!g_bopengl && m_singleChart &&
4481 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4482 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4483 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4484 (m_singleChart->GetChartProjectionType() !=
4485 PROJECTION_TRANSVERSE_MERCATOR) &&
4486 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4487 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4488 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4489 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4490 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4491 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4492 // Cur_BSB_Ch->GetCOVRTablenPoints
4493 // ( 0 ), rlon,
4494 // rlat );
4495 // bInside = true;
4496 // if ( bInside )
4497 if (Cur_BSB_Ch) {
4498 // This is a Raster chart....
4499 // If the VP is changing, the raster chart parameters may not yet be
4500 // setup So do that before accessing the chart's embedded
4501 // georeferencing
4502 Cur_BSB_Ch->SetVPRasterParms(vp);
4503 double rpixxd, rpixyd;
4504 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4505 r->m_x = rpixxd;
4506 r->m_y = rpixyd;
4507 return;
4508 }
4509 }
4510 }
4511
4512 // if needed, use the VPoint scaling estimator,
4513 *r = vp.GetDoublePixFromLL(rlat, rlon);
4514}
4515
4516// This routine might be deleted and all of the rendering improved
4517// to have floating point accuracy
4518bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4519 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4520}
4521
4522bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4523 wxPoint *r) {
4524 wxPoint2DDouble p;
4525 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4526
4527 // some projections give nan values when invisible values (other side of
4528 // world) are requested we should stop using integer coordinates or return
4529 // false here (and test it everywhere)
4530 if (std::isnan(p.m_x)) {
4531 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4532 return false;
4533 }
4534
4535 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4536 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4537 else
4538 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4539
4540 return true;
4541}
4542
4543void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4544 double &lon) {
4545 // If the Current Chart is a raster chart, and the
4546 // requested x,y is within the boundaries of the chart,
4547 // and the VP is not rotated,
4548 // then use the embedded BSB chart georeferencing algorithm
4549 // for greater accuracy
4550 // Additionally, use chart embedded georef if the projection is TMERC
4551 // i.e. NOT MERCATOR and NOT POLYCONIC
4552
4553 // If for some reason the chart rejects the request by returning an error,
4554 // then fall back to Viewport Projection estimate from canvas parameters
4555 bool bUseVP = true;
4556
4557 if (!g_bopengl && m_singleChart &&
4558 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4559 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4560 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4561 (m_singleChart->GetChartProjectionType() !=
4562 PROJECTION_TRANSVERSE_MERCATOR) &&
4563 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4564 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4565 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4566 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4567
4568 // TODO maybe need iterative process to validate bInside
4569 // first pass is mercator, then check chart boundaries
4570
4571 if (Cur_BSB_Ch) {
4572 // This is a Raster chart....
4573 // If the VP is changing, the raster chart parameters may not yet be
4574 // setup So do that before accessing the chart's embedded
4575 // georeferencing
4576 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4577
4578 double slat, slon;
4579 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4580 lat = slat;
4581
4582 if (slon < -180.)
4583 slon += 360.;
4584 else if (slon > 180.)
4585 slon -= 360.;
4586
4587 lon = slon;
4588 bUseVP = false;
4589 }
4590 }
4591 }
4592
4593 // if needed, use the VPoint scaling estimator
4594 if (bUseVP) {
4595 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4596 }
4597}
4598
4600 DoZoomCanvas(factor, false);
4601 extendedSectorLegs.clear();
4602}
4603
4604void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4605 bool stoptimer) {
4606 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4607
4608 if (g_bsmoothpanzoom) {
4609 if (StartTimedMovement(stoptimer)) {
4610 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4611 m_zoom_factor = factor;
4612 }
4613
4614 m_zoom_target = VPoint.chart_scale / factor;
4615 } else {
4616 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4617
4618 DoZoomCanvas(factor, can_zoom_to_cursor);
4619 }
4620
4621 extendedSectorLegs.clear();
4622}
4623
4624void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4625 // possible on startup
4626 if (!ChartData) return;
4627 if (!m_pCurrentStack) return;
4628
4629 /* TODO: queue the quilted loading code to a background thread
4630 so yield is never called from here, and also rendering is not delayed */
4631
4632 // Cannot allow Yield() re-entrancy here
4633 if (m_bzooming) return;
4634 m_bzooming = true;
4635
4636 double old_ppm = GetVP().view_scale_ppm;
4637
4638 // Capture current cursor position for zoom to cursor
4639 double zlat = m_cursor_lat;
4640 double zlon = m_cursor_lon;
4641
4642 double proposed_scale_onscreen =
4643 GetVP().chart_scale /
4644 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4645 bool b_do_zoom = false;
4646
4647 if (factor > 1) {
4648 b_do_zoom = true;
4649
4650 // double zoom_factor = factor;
4651
4652 ChartBase *pc = NULL;
4653
4654 if (!VPoint.b_quilt) {
4655 pc = m_singleChart;
4656 } else {
4657 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4658 if (new_db_index >= 0)
4659 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4660 else { // for whatever reason, no reference chart is known
4661 // Choose the smallest scale chart on the current stack
4662 // and then adjust for scale range
4663 int current_ref_stack_index = -1;
4664 if (m_pCurrentStack->nEntry) {
4665 int trial_index =
4666 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4667 m_pQuilt->SetReferenceChart(trial_index);
4668 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4669 if (new_db_index >= 0)
4670 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4671 }
4672 }
4673
4674 if (m_pCurrentStack)
4675 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4676 new_db_index); // highlite the correct bar entry
4677 }
4678
4679 if (pc) {
4680 // double target_scale_ppm = GetVPScale() * zoom_factor;
4681 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4682 // target_scale_ppm;
4683
4684 // Query the chart to determine the appropriate zoom range
4685 double min_allowed_scale =
4686 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4687
4688 if (proposed_scale_onscreen < min_allowed_scale) {
4689 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4690 m_zoom_factor = 1; /* stop zooming */
4691 b_do_zoom = false;
4692 } else
4693 proposed_scale_onscreen = min_allowed_scale;
4694 }
4695
4696 } else {
4697 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4698 }
4699
4700 } else if (factor < 1) {
4701 b_do_zoom = true;
4702
4703 ChartBase *pc = NULL;
4704
4705 bool b_smallest = false;
4706
4707 if (!VPoint.b_quilt) { // not quilted
4708 pc = m_singleChart;
4709
4710 if (pc) {
4711 // If m_singleChart is not on the screen, unbound the zoomout
4712 LLBBox viewbox = VPoint.GetBBox();
4713 // BoundingBox chart_box;
4714 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4715 double max_allowed_scale;
4716
4717 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4718
4719 // We can allow essentially unbounded zoomout in single chart mode
4720 // if( ChartData->GetDBBoundingBox( current_index,
4721 // &chart_box ) &&
4722 // !viewbox.IntersectOut( chart_box ) )
4723 // // Clamp the minimum scale zoom-out to the value
4724 // specified by the chart max_allowed_scale =
4725 // wxMin(max_allowed_scale, 4.0 *
4726 // pc->GetNormalScaleMax(
4727 // GetCanvasScaleFactor(),
4728 // GetCanvasWidth() ) );
4729 if (proposed_scale_onscreen > max_allowed_scale) {
4730 m_zoom_factor = 1; /* stop zooming */
4731 proposed_scale_onscreen = max_allowed_scale;
4732 }
4733 }
4734
4735 } else {
4736 int new_db_index = m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4737 if (new_db_index >= 0)
4738 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4739
4740 if (m_pCurrentStack)
4741 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4742 new_db_index); // highlite the correct bar entry
4743
4744 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4745
4746 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4747 proposed_scale_onscreen =
4748 wxMin(proposed_scale_onscreen,
4749 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4750 }
4751
4752 // set a minimum scale
4753 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4754 m_absolute_min_scale_ppm)
4755 b_do_zoom = false;
4756 }
4757
4758 double new_scale =
4759 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4760
4761 if (b_do_zoom) {
4762 // Disable ZTC if lookahead is ON, and currently b_follow is active
4763 bool b_allow_ztc = true;
4764 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4765 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4766 if (m_bLookAhead) {
4767 double brg, distance;
4768 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4769 &distance);
4770 dir_to_shift = brg;
4771 meters_to_shift = distance * 1852;
4772 }
4773 // Arrange to combine the zoom and pan into one operation for smoother
4774 // appearance
4775 SetVPScale(new_scale, false); // adjust, but deferred refresh
4776 wxPoint r;
4777 GetCanvasPointPix(zlat, zlon, &r);
4778 // this will emit the Refresh()
4779 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4780 } else {
4781 SetVPScale(new_scale);
4782 if (m_bFollow) DoCanvasUpdate();
4783 }
4784 }
4785
4786 m_bzooming = false;
4787}
4788int rot;
4789void ChartCanvas::RotateCanvas(double dir) {
4790 // SetUpMode(NORTH_UP_MODE);
4791
4792 if (g_bsmoothpanzoom) {
4793 if (StartTimedMovement()) {
4794 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4795 m_rotation_speed = dir * 60;
4796 }
4797 } else {
4798 double speed = dir * 10;
4799 if (m_modkeys == wxMOD_ALT) speed /= 20;
4800 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4801 }
4802}
4803
4804void ChartCanvas::DoRotateCanvas(double rotation) {
4805 while (rotation < 0) rotation += 2 * PI;
4806 while (rotation > 2 * PI) rotation -= 2 * PI;
4807
4808 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4809
4810 SetVPRotation(rotation);
4811 parent_frame->UpdateRotationState(VPoint.rotation);
4812}
4813
4814void ChartCanvas::DoTiltCanvas(double tilt) {
4815 while (tilt < 0) tilt = 0;
4816 while (tilt > .95) tilt = .95;
4817
4818 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4819
4820 VPoint.tilt = tilt;
4821 Refresh(false);
4822}
4823
4824void ChartCanvas::TogglebFollow(void) {
4825 if (!m_bFollow)
4826 SetbFollow();
4827 else
4828 ClearbFollow();
4829}
4830
4831void ChartCanvas::ClearbFollow(void) {
4832 m_bFollow = false; // update the follow flag
4833
4834 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4835
4836 UpdateFollowButtonState();
4837
4838 DoCanvasUpdate();
4839 ReloadVP();
4840 parent_frame->SetChartUpdatePeriod();
4841}
4842
4843void ChartCanvas::SetbFollow(void) {
4844 // Is the OWNSHIP on-screen?
4845 // If not, then reset the OWNSHIP offset to 0 (center screen)
4846 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4847 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4848 m_OSoffsetx = 0;
4849 m_OSoffsety = 0;
4850 }
4851
4852 // Apply the present b_follow offset values to ship position
4853 wxPoint2DDouble p;
4854 GetDoubleCanvasPointPix(gLat, gLon, &p);
4855 p.m_x += m_OSoffsetx;
4856 p.m_y -= m_OSoffsety;
4857
4858 // compute the target center screen lat/lon
4859 double dlat, dlon;
4860 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4861
4862 JumpToPosition(dlat, dlon, GetVPScale());
4863 m_bFollow = true;
4864
4865 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4866 UpdateFollowButtonState();
4867
4868 if (!g_bSmoothRecenter) {
4869 DoCanvasUpdate();
4870 ReloadVP();
4871 }
4872 parent_frame->SetChartUpdatePeriod();
4873}
4874
4875void ChartCanvas::UpdateFollowButtonState(void) {
4876 if (m_muiBar) {
4877 if (!m_bFollow)
4878 m_muiBar->SetFollowButtonState(0);
4879 else {
4880 if (m_bLookAhead)
4881 m_muiBar->SetFollowButtonState(2);
4882 else
4883 m_muiBar->SetFollowButtonState(1);
4884 }
4885 }
4886
4887#ifdef __ANDROID__
4888 if (!m_bFollow)
4889 androidSetFollowTool(0);
4890 else {
4891 if (m_bLookAhead)
4892 androidSetFollowTool(2);
4893 else
4894 androidSetFollowTool(1);
4895 }
4896#endif
4897}
4898
4899void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4900 if (g_bSmoothRecenter && !m_routeState) {
4901 if (StartSmoothJump(lat, lon, scale_ppm))
4902 return;
4903 else {
4904 // move closer to the target destination, and try again
4905 double gcDist, gcBearingEnd;
4906 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4907 &gcBearingEnd);
4908 gcBearingEnd += 180;
4909 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4910 GetCanvasWidth() / GetVPScale(); // meters
4911 double lon_offset =
4912 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4913 double new_lat = lat + (lat_offset / (1852 * 60));
4914 double new_lon = lon + (lon_offset / (1852 * 60));
4915 SetViewPoint(new_lat, new_lon);
4916 ReloadVP();
4917 StartSmoothJump(lat, lon, scale_ppm);
4918 return;
4919 }
4920 }
4921
4922 if (lon > 180.0) lon -= 360.0;
4923 m_vLat = lat;
4924 m_vLon = lon;
4925 StopMovement();
4926 m_bFollow = false;
4927
4928 if (!GetQuiltMode()) {
4929 double skew = 0;
4930 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4931 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4932 } else {
4933 if (scale_ppm != GetVPScale()) {
4934 // XXX should be done in SetViewPoint
4935 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4936 AdjustQuiltRefChart();
4937 }
4938 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4939 }
4940
4941 ReloadVP();
4942
4943 UpdateFollowButtonState();
4944
4945 // TODO
4946 // if( g_pi_manager ) {
4947 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4948 // }
4949}
4950
4951bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
4952 // Check distance to jump, in pixels at current chart scale
4953 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
4954 // width.
4955 double gcDist;
4956 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
4957 double distance_pixels = gcDist * GetVPScale();
4958 if (distance_pixels > 0.5 * GetCanvasWidth()) {
4959 // Jump is too far, try again
4960 return false;
4961 }
4962
4963 // Save where we're coming from
4964 m_startLat = m_vLat;
4965 m_startLon = m_vLon;
4966 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
4967
4968 // Save where we want to end up
4969 m_endLat = lat;
4970 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
4971 m_endScale = scale_ppm;
4972
4973 // Setup timing
4974 m_animationDuration = 600; // ms
4975 m_animationStart = wxGetLocalTimeMillis();
4976
4977 // Stop any previous movement, ensure no conflicts
4978 StopMovement();
4979 m_bFollow = false;
4980
4981 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
4982 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
4983 m_animationActive = true;
4984
4985 return true;
4986}
4987
4988void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
4989 // Calculate time fraction from 0..1
4990 wxLongLong now = wxGetLocalTimeMillis();
4991 double elapsed = (now - m_animationStart).ToDouble();
4992 double t = elapsed / m_animationDuration.ToDouble();
4993 if (t > 1.0) t = 1.0;
4994
4995 // Ease function for smoother movement
4996 double e = easeOutCubic(t);
4997
4998 // Interpolate lat/lon/scale
4999 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5000 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5001 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5002
5003 // Update viewpoint
5004 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5005 // portion)
5006 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5007 ReloadVP();
5008
5009 // If we reached the end, stop the timer and finalize
5010 if (t >= 1.0) {
5011 m_easeTimer.Stop();
5012 m_animationActive = false;
5013 UpdateFollowButtonState();
5014 DoCanvasUpdate();
5015 ReloadVP();
5016 }
5017}
5018
5019bool ChartCanvas::PanCanvas(double dx, double dy) {
5020 if (!ChartData) return false;
5021
5022 if (g_btouch) {
5023 // Stop bfollow state, without a refresh
5024 m_bFollow = false; // update the follow flag
5025 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
5026 UpdateFollowButtonState();
5027 // Clear the bfollow offset
5028 m_OSoffsetx = 0;
5029 m_OSoffsety = 0;
5030 }
5031
5032 extendedSectorLegs.clear();
5033
5034 // double clat = VPoint.clat, clon = VPoint.clon;
5035 double dlat, dlon;
5036 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5037
5038 int iters = 0;
5039 for (;;) {
5040 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5041
5042 if (iters++ > 5) return false;
5043 if (!std::isnan(dlat)) break;
5044
5045 dx *= .5, dy *= .5;
5046 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5047 }
5048
5049 // avoid overshooting the poles
5050 if (dlat > 90)
5051 dlat = 90;
5052 else if (dlat < -90)
5053 dlat = -90;
5054
5055 if (dlon > 360.) dlon -= 360.;
5056 if (dlon < -360.) dlon += 360.;
5057
5058 // This should not really be necessary, but round-trip georef on some
5059 // charts is not perfect, So we can get creep on repeated unidimensional
5060 // pans, and corrupt chart cacheing.......
5061
5062 // But this only works on north-up projections
5063 // TODO: can we remove this now?
5064 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5065 // .001 ) ) {
5066 //
5067 // if( dx == 0 ) dlon = clon;
5068 // if( dy == 0 ) dlat = clat;
5069 // }
5070
5071 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5072
5073 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5074
5075 if (VPoint.b_quilt) {
5076 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5077 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5078 // Tweak the scale slightly for a new ref chart
5079 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5080 if (pc) {
5081 double tweak_scale_ppm =
5082 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5083 SetVPScale(tweak_scale_ppm);
5084 }
5085 }
5086
5087 if (new_ref_dbIndex == -1) {
5088 // for whatever reason, no reference chart is known
5089 // Probably panned out of the coverage region
5090 // If any charts are anywhere on-screen, choose the smallest
5091 // scale chart on the screen to be a new reference chart.
5092 int trial_index = -1;
5093 if (m_pCurrentStack->nEntry) {
5094 int trial_index =
5095 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5096 }
5097
5098 if (trial_index < 0) {
5099 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5100 if (full_screen_array.size())
5101 trial_index = full_screen_array[full_screen_array.size() - 1];
5102 }
5103
5104 if (trial_index >= 0) {
5105 m_pQuilt->SetReferenceChart(trial_index);
5106 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5107 VPoint.rotation);
5108 ReloadVP();
5109 }
5110 }
5111 }
5112
5113 // Turn off bFollow only if the ownship has left the screen
5114 if (m_bFollow) {
5115 double offx, offy;
5116 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5117
5118 double offset_angle = atan2(offy, offx);
5119 double offset_distance = sqrt((offy * offy) + (offx * offx));
5120 double chart_angle = GetVPRotation();
5121 double target_angle = chart_angle - offset_angle;
5122 double d_east_mod = offset_distance * cos(target_angle);
5123 double d_north_mod = offset_distance * sin(target_angle);
5124
5125 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5126 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5127
5128 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5129 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5130 m_bFollow = false; // update the follow flag
5131 UpdateFollowButtonState();
5132 }
5133 }
5134
5135 Refresh(false);
5136
5137 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5138
5139 return true;
5140}
5141
5142void ChartCanvas::ReloadVP(bool b_adjust) {
5143 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5144
5145 LoadVP(VPoint, b_adjust);
5146}
5147
5148void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5149#ifdef ocpnUSE_GL
5150 if (g_bopengl && m_glcc) {
5151 m_glcc->Invalidate();
5152 if (m_glcc->GetSize() != GetSize()) {
5153 m_glcc->SetSize(GetSize());
5154 }
5155 } else
5156#endif
5157 {
5158 m_cache_vp.Invalidate();
5159 m_bm_cache_vp.Invalidate();
5160 }
5161
5162 VPoint.Invalidate();
5163
5164 if (m_pQuilt) m_pQuilt->Invalidate();
5165
5166 // Make sure that the Selected Group is sensible...
5167 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5168 // m_groupIndex = 0;
5169 // if( !CheckGroup( m_groupIndex ) )
5170 // m_groupIndex = 0;
5171
5172 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5173 vp.m_projection_type, b_adjust);
5174}
5175
5176void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5177 m_pQuilt->SetReferenceChart(dbIndex);
5178 VPoint.Invalidate();
5179 m_pQuilt->Invalidate();
5180}
5181
5182double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5183 if (m_pQuilt)
5184 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5185 else
5186 return vp.view_scale_ppm;
5187}
5188
5189// Verify and adjust the current reference chart,
5190// so that it will not lead to excessive overzoom or underzoom onscreen
5191int ChartCanvas::AdjustQuiltRefChart() {
5192 int ret = -1;
5193 if (m_pQuilt) {
5194 wxASSERT(ChartData);
5195 ChartBase *pc =
5196 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5197 if (pc) {
5198 double min_ref_scale =
5199 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5200 double max_ref_scale =
5201 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5202
5203 if (VPoint.chart_scale < min_ref_scale) {
5204 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5205 } else if (VPoint.chart_scale > max_ref_scale) {
5206 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5207 } else {
5208 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5209
5210 int ref_family = pc->GetChartFamily();
5211
5212 if (!brender_ok) {
5213 unsigned int target_stack_index = 0;
5214 int target_stack_index_check =
5215 m_pQuilt->GetExtendedStackIndexArray()
5216 [m_pQuilt->GetRefChartdbIndex()]; // Lookup
5217
5218 if (wxNOT_FOUND != target_stack_index_check)
5219 target_stack_index = target_stack_index_check;
5220
5221 int extended_array_count =
5222 m_pQuilt->GetExtendedStackIndexArray().size();
5223 while ((!brender_ok) &&
5224 ((int)target_stack_index < (extended_array_count - 1))) {
5225 target_stack_index++;
5226 int test_db_index =
5227 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5228
5229 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5230 IsChartQuiltableRef(test_db_index)) {
5231 // open the target, and check the min_scale
5232 ChartBase *ptest_chart =
5233 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5234 if (ptest_chart) {
5235 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5236 }
5237 }
5238 }
5239
5240 if (brender_ok) { // found a better reference chart
5241 int new_db_index =
5242 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5243 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5244 IsChartQuiltableRef(new_db_index)) {
5245 m_pQuilt->SetReferenceChart(new_db_index);
5246 ret = new_db_index;
5247 } else
5248 ret = m_pQuilt->GetRefChartdbIndex();
5249 } else
5250 ret = m_pQuilt->GetRefChartdbIndex();
5251
5252 } else
5253 ret = m_pQuilt->GetRefChartdbIndex();
5254 }
5255 } else
5256 ret = -1;
5257 }
5258
5259 return ret;
5260}
5261
5262void ChartCanvas::UpdateCanvasOnGroupChange(void) {
5263 delete m_pCurrentStack;
5264 m_pCurrentStack = new ChartStack;
5265 wxASSERT(ChartData);
5266 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5267 m_groupIndex);
5268
5269 if (m_pQuilt) {
5270 m_pQuilt->Compose(VPoint);
5271 SetFocus();
5272 }
5273}
5274
5275bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5276 double latNE, double lonNE) {
5277 // Center Point
5278 double latc = (latSW + latNE) / 2.0;
5279 double lonc = (lonSW + lonNE) / 2.0;
5280
5281 // Get scale in ppm (latitude)
5282 double ne_easting, ne_northing;
5283 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5284
5285 double sw_easting, sw_northing;
5286 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5287
5288 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5289
5290 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5291}
5292
5293bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5294 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5295 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5296}
5297
5298bool ChartCanvas::SetVPProjection(int projection) {
5299 if (!g_bopengl) // alternative projections require opengl
5300 return false;
5301
5302 // the view scale varies depending on geographic location and projection
5303 // rescale to keep the relative scale on the screen the same
5304 double prev_true_scale_ppm = m_true_scale_ppm;
5305 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5306 VPoint.skew, VPoint.rotation, projection) &&
5307 SetVPScale(wxMax(
5308 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5309 m_absolute_min_scale_ppm));
5310}
5311
5312bool ChartCanvas::SetViewPoint(double lat, double lon) {
5313 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5314 VPoint.rotation);
5315}
5316
5317bool ChartCanvas::SetVPRotation(double angle) {
5318 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5319 VPoint.skew, angle);
5320}
5321bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5322 double skew, double rotation, int projection,
5323 bool b_adjust, bool b_refresh) {
5324 bool b_ret = false;
5325 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5326 skew -= 2 * PI;
5327 // Any sensible change?
5328 if (VPoint.IsValid()) {
5329 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5330 (fabs(VPoint.skew - skew) < 1e-9) &&
5331 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5332 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5333 (VPoint.m_projection_type == projection ||
5334 projection == PROJECTION_UNKNOWN))
5335 return false;
5336 }
5337 if (VPoint.m_projection_type != projection)
5338 VPoint.InvalidateTransformCache(); // invalidate
5339
5340 // Take a local copy of the last viewport
5341 ViewPort last_vp = VPoint;
5342
5343 VPoint.skew = skew;
5344 VPoint.clat = lat;
5345 VPoint.clon = lon;
5346 VPoint.rotation = rotation;
5347 VPoint.view_scale_ppm = scale_ppm;
5348 if (projection != PROJECTION_UNKNOWN)
5349 VPoint.SetProjectionType(projection);
5350 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5351 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5352
5353 // don't allow latitude above 88 for mercator (90 is infinity)
5354 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5355 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5356 if (VPoint.clat > 89.5)
5357 VPoint.clat = 89.5;
5358 else if (VPoint.clat < -89.5)
5359 VPoint.clat = -89.5;
5360 }
5361
5362 // don't zoom out too far for transverse mercator polyconic until we resolve
5363 // issues
5364 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5365 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5366 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5367
5368 // SetVPRotation(rotation);
5369
5370 if (!g_bopengl) // tilt is not possible without opengl
5371 VPoint.tilt = 0;
5372
5373 if ((VPoint.pix_width <= 0) ||
5374 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5375 return false;
5376
5377 bool bwasValid = VPoint.IsValid();
5378 VPoint.Validate(); // Mark this ViewPoint as OK
5379
5380 // Has the Viewport scale changed? If so, invalidate the vp
5381 if (last_vp.view_scale_ppm != scale_ppm) {
5382 m_cache_vp.Invalidate();
5383 InvalidateGL();
5384 }
5385
5386 // A preliminary value, may be tweaked below
5387 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5388
5389 // recompute cursor position
5390 // and send to interested plugins if the mouse is actually in this window
5391
5392 const wxPoint pt = wxGetMousePosition();
5393 int mouseX = pt.x - GetScreenPosition().x;
5394 int mouseY = pt.y - GetScreenPosition().y;
5395 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5396 (mouseY < VPoint.pix_height)) {
5397 double lat, lon;
5398 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5399 m_cursor_lat = lat;
5400 m_cursor_lon = lon;
5401 SendCursorLatLonToAllPlugIns(lat, lon);
5402 }
5403
5404 if (!VPoint.b_quilt && m_singleChart) {
5405 VPoint.SetBoxes();
5406
5407 // Allow the chart to adjust the new ViewPort for performance optimization
5408 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5409 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5410
5411 // If there is a sensible change in the chart render, refresh the whole
5412 // screen
5413 if ((!m_cache_vp.IsValid()) ||
5414 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5415 Refresh(false);
5416 b_ret = true;
5417 } else {
5418 wxPoint cp_last, cp_this;
5419 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5420 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5421
5422 if (cp_last != cp_this) {
5423 Refresh(false);
5424 b_ret = true;
5425 }
5426 }
5427 // Create the stack
5428 if (m_pCurrentStack) {
5429 assert(ChartData != 0);
5430 int current_db_index;
5431 current_db_index =
5432 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5433
5434 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5435 m_groupIndex);
5436 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5437 }
5438
5439 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5440 }
5441
5442 // Handle the quilted case
5443 if (VPoint.b_quilt) {
5444 if (last_vp.view_scale_ppm != scale_ppm)
5445 m_pQuilt->InvalidateAllQuiltPatchs();
5446
5447 // Create the quilt
5448 if (ChartData /*&& ChartData->IsValid()*/) {
5449 if (!m_pCurrentStack) return false;
5450
5451 int current_db_index;
5452 current_db_index =
5453 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5454
5455 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5456 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5457
5458 // Check to see if the current quilt reference chart is in the new stack
5459 int current_ref_stack_index = -1;
5460 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5461 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5462 current_ref_stack_index = i;
5463 }
5464
5465 if (g_bFullScreenQuilt) {
5466 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5467 }
5468
5469 // We might need a new Reference Chart
5470 bool b_needNewRef = false;
5471
5472 // If the new stack does not contain the current ref chart....
5473 if ((-1 == current_ref_stack_index) &&
5474 (m_pQuilt->GetRefChartdbIndex() >= 0))
5475 b_needNewRef = true;
5476
5477 // Would the current Ref Chart be excessively underzoomed?
5478 // We need to check this here to be sure, since we cannot know where the
5479 // reference chart was assigned. For instance, the reference chart may
5480 // have been selected from the config file, or from a long jump with a
5481 // chart family switch implicit. Anyway, we check to be sure....
5482 bool renderable = true;
5483 ChartBase *referenceChart =
5484 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5485 if (referenceChart) {
5486 double chartMaxScale = referenceChart->GetNormalScaleMax(
5487 GetCanvasScaleFactor(), GetCanvasWidth());
5488 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5489 }
5490 if (!renderable) b_needNewRef = true;
5491
5492 // Need new refchart?
5493 if (b_needNewRef) {
5494 const ChartTableEntry &cte_ref =
5495 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5496 int target_scale = cte_ref.GetScale();
5497 int target_type = cte_ref.GetChartType();
5498 int candidate_stack_index;
5499
5500 // reset the ref chart in a way that does not lead to excessive
5501 // underzoom, for performance reasons Try to find a chart that is the
5502 // same type, and has a scale of just smaller than the current ref
5503 // chart
5504
5505 candidate_stack_index = 0;
5506 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5507 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5508 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5509 int candidate_scale = cte_candidate.GetScale();
5510 int candidate_type = cte_candidate.GetChartType();
5511
5512 if ((candidate_scale >= target_scale) &&
5513 (candidate_type == target_type)) {
5514 bool renderable = true;
5515 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5516 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5517 if (tentative_referenceChart) {
5518 double chartMaxScale =
5519 tentative_referenceChart->GetNormalScaleMax(
5520 GetCanvasScaleFactor(), GetCanvasWidth());
5521 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5522 }
5523
5524 if (renderable) break;
5525 }
5526
5527 candidate_stack_index++;
5528 }
5529
5530 // If that did not work, look for a chart of just larger scale and
5531 // same type
5532 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5533 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5534 while (candidate_stack_index >= 0) {
5535 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5536 if (idx >= 0) {
5537 const ChartTableEntry &cte_candidate =
5538 ChartData->GetChartTableEntry(idx);
5539 int candidate_scale = cte_candidate.GetScale();
5540 int candidate_type = cte_candidate.GetChartType();
5541
5542 if ((candidate_scale <= target_scale) &&
5543 (candidate_type == target_type))
5544 break;
5545 }
5546 candidate_stack_index--;
5547 }
5548 }
5549
5550 // and if that did not work, chose stack entry 0
5551 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5552 (candidate_stack_index < 0))
5553 candidate_stack_index = 0;
5554
5555 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5556
5557 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5558 }
5559
5560 if (!g_bopengl) {
5561 // Preset the VPoint projection type to match what the quilt projection
5562 // type will be
5563 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5564
5565 // Always keep the default Mercator projection if the reference chart is
5566 // not in the PatchList or the scale is too small for it to render.
5567
5568 bool renderable = true;
5569 ChartBase *referenceChart =
5570 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5571 if (referenceChart) {
5572 double chartMaxScale = referenceChart->GetNormalScaleMax(
5573 GetCanvasScaleFactor(), GetCanvasWidth());
5574 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5575 proj = ChartData->GetDBChartProj(ref_db_index);
5576 } else
5577 proj = PROJECTION_MERCATOR;
5578
5579 VPoint.b_MercatorProjectionOverride =
5580 (m_pQuilt->GetnCharts() == 0 || !renderable);
5581
5582 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5583
5584 VPoint.SetProjectionType(proj);
5585 }
5586
5587 VPoint.SetBoxes();
5588
5589 // If this quilt will be a perceptible delta from the existing quilt,
5590 // then refresh the entire screen
5591 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5592 // Allow the quilt to adjust the new ViewPort for performance
5593 // optimization This will normally be only a fractional (i.e.
5594 // sub-pixel) adjustment...
5595 if (b_adjust) m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5596
5597 // ChartData->ClearCacheInUseFlags();
5598 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5599
5600 // wxStopWatch sw;
5601
5602#ifdef __ANDROID__
5603 // This is an optimization for panning on touch screen systems.
5604 // The quilt composition is deferred until the OnPaint() message gets
5605 // finally removed and processed from the message queue.
5606 // Takes advantage of the fact that touch-screen pan gestures are
5607 // usually short in distance,
5608 // so not requiring a full quilt rebuild until the pan gesture is
5609 // complete.
5610 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5611 // qDebug() << "Force compose";
5612 m_pQuilt->Compose(VPoint);
5613 } else {
5614 m_pQuilt->Invalidate();
5615 }
5616#else
5617 m_pQuilt->Compose(VPoint);
5618#endif
5619
5620 // printf("comp time %ld\n", sw.Time());
5621
5622 // If the extended chart stack has changed, invalidate any cached
5623 // render bitmap
5624 // if(m_pQuilt->GetXStackHash() != hash1) {
5625 // m_bm_cache_vp.Invalidate();
5626 // InvalidateGL();
5627 // }
5628
5629 ChartData->PurgeCacheUnusedCharts(0.7);
5630
5631 if (b_refresh) Refresh(false);
5632
5633 b_ret = true;
5634 }
5635 }
5636
5637 VPoint.skew = 0.; // Quilting supports 0 Skew
5638 } else if (!g_bopengl) {
5639 OcpnProjType projection = PROJECTION_UNKNOWN;
5640 if (m_singleChart) // viewport projection must match chart projection
5641 // without opengl
5642 projection = m_singleChart->GetChartProjectionType();
5643 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5644 VPoint.SetProjectionType(projection);
5645 }
5646
5647 // Has the Viewport projection changed? If so, invalidate the vp
5648 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5649 m_cache_vp.Invalidate();
5650 InvalidateGL();
5651 }
5652
5653 UpdateCanvasControlBar(); // Refresh the Piano
5654
5655 VPoint.chart_scale = 1.0; // fallback default value
5656
5657 /*if (!VPoint.GetBBox().GetValid())*/ VPoint.SetBoxes();
5658
5659 if (VPoint.GetBBox().GetValid()) {
5660 // Update the viewpoint reference scale
5661 if (m_singleChart)
5662 VPoint.ref_scale = m_singleChart->GetNativeScale();
5663 else {
5664#ifdef __ANDROID__
5665 // This is an optimization for panning on touch screen systems.
5666 // See above.
5667 // Quilt might not be fully composed at this point, so for cm93
5668 // the reference scale may not be known.
5669 // In this case, do not update the VP ref_scale.
5670 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5671 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5672 }
5673#else
5674 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5675#endif
5676 }
5677
5678 // Calculate the on-screen displayed actual scale
5679 // by a simple traverse northward from the center point
5680 // of roughly one eighth of the canvas height
5681 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5682
5683 double delta_check =
5684 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5685 delta_check /= 8.;
5686
5687 double check_point = wxMin(89., VPoint.clat);
5688
5689 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5690
5691 double rhumbDist;
5692 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5693 VPoint.clon, 0, &rhumbDist);
5694
5695 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5696 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5697 // Calculate the distance between r1 and r in physical pixels.
5698 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5699 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5700
5701 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5702
5703 // A fall back in case of very high zoom-out, giving delta_y == 0
5704 // which can probably only happen with vector charts
5705 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5706
5707 // Another fallback, for highly zoomed out charts
5708 // This adjustment makes the displayed TrueScale correspond to the
5709 // same algorithm used to calculate the chart zoom-out limit for
5710 // ChartDummy.
5711 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5712
5713 if (m_true_scale_ppm)
5714 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5715 else
5716 VPoint.chart_scale = 1.0;
5717
5718 // Create a nice renderable string
5719 double round_factor = 1000.;
5720 if (VPoint.chart_scale <= 1000.)
5721 round_factor = 10.;
5722 else if (VPoint.chart_scale <= 10000.)
5723 round_factor = 100.;
5724 else if (VPoint.chart_scale <= 100000.)
5725 round_factor = 1000.;
5726
5727 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5728 double retina_coef = 1;
5729#ifdef ocpnUSE_GL
5730#ifdef __WXOSX__
5731 if (g_bopengl) {
5732 retina_coef = GetContentScaleFactor();
5733 }
5734#endif
5735#endif
5736
5737 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5738 // rounded to the nearest 10, 100 or 1000.
5739 //
5740 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5741 // true_scale_display. That does not make sense. The chart scale should be
5742 // the same as the true scale within the limits of the rounding factor.
5743 double true_scale_display =
5744 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5745 wxString text;
5746
5747 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5748
5749 if (m_displayed_scale_factor > 10.0)
5750 text.Printf(_T("%s %4.0f (%1.0fx)"), _("Scale"), true_scale_display,
5751 m_displayed_scale_factor);
5752 else if (m_displayed_scale_factor > 1.0)
5753 text.Printf(_T("%s %4.0f (%1.1fx)"), _("Scale"), true_scale_display,
5754 m_displayed_scale_factor);
5755 else if (m_displayed_scale_factor > 0.1) {
5756 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5757 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5758 } else if (m_displayed_scale_factor > 0.01) {
5759 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5760 text.Printf(_T("%s %4.0f (%1.2fx)"), _("Scale"), true_scale_display, sfr);
5761 } else {
5762 text.Printf(
5763 _T("%s %4.0f (---)"), _("Scale"),
5764 true_scale_display); // Generally, no chart, so no chart scale factor
5765 }
5766
5767 m_scaleValue = true_scale_display;
5768 m_scaleText = text;
5769 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5770
5771 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5772 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5773 // Check to see if the text will fit in the StatusBar field...
5774 bool b_noshow = false;
5775 {
5776 int w = 0;
5777 int h;
5778 wxClientDC dc(parent_frame->GetStatusBar());
5779 if (dc.IsOk()) {
5780 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5781 dc.SetFont(*templateFont);
5782 dc.GetTextExtent(text, &w, &h);
5783
5784 // If text is too long for the allocated field, try to reduce the text
5785 // string a bit.
5786 wxRect rect;
5787 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5788 if (w && w > rect.width) {
5789 text.Printf(_T("%s (%1.1fx)"), _("Scale"),
5790 m_displayed_scale_factor);
5791 }
5792
5793 // Test again...if too big still, then give it up.
5794 dc.GetTextExtent(text, &w, &h);
5795
5796 if (w && w > rect.width) {
5797 b_noshow = true;
5798 }
5799 }
5800 }
5801
5802 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5803 }
5804 }
5805
5806 // Maintain member vLat/vLon
5807 m_vLat = VPoint.clat;
5808 m_vLon = VPoint.clon;
5809
5810 return b_ret;
5811}
5812
5813// Static Icon definitions for some symbols requiring
5814// scaling/rotation/translation Very specific wxDC draw commands are
5815// necessary to properly render these icons...See the code in
5816// ShipDraw()
5817
5818// This icon was adapted and scaled from the S52 Presentation Library
5819// version 3_03.
5820// Symbol VECGND02
5821
5822static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5823
5824// This ownship icon was adapted and scaled from the S52 Presentation
5825// Library version 3_03 Symbol OWNSHP05
5826static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5827 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5828
5829wxColour ChartCanvas::PredColor() {
5830 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5831 // visibility.
5832 if (SHIP_NORMAL == m_ownship_state)
5833 return GetGlobalColor(_T ( "URED" ));
5834
5835 else if (SHIP_LOWACCURACY == m_ownship_state)
5836 return GetGlobalColor(_T ( "YELO1" ));
5837
5838 return GetGlobalColor(_T ( "NODTA" ));
5839}
5840
5841wxColour ChartCanvas::ShipColor() {
5842 // Establish ship color
5843 // It changes color based on GPS and Chart accuracy/availability
5844
5845 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor(_T ( "GREY1" ));
5846
5847 if (SHIP_LOWACCURACY == m_ownship_state)
5848 return GetGlobalColor(_T ( "YELO1" ));
5849
5850 return GetGlobalColor(_T ( "URED" )); // default is OK
5851}
5852
5853void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5854 wxPoint2DDouble lShipMidPoint) {
5855 dc.SetPen(wxPen(PredColor(), 2));
5856
5857 if (SHIP_NORMAL == m_ownship_state)
5858 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5859 else
5860 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "YELO1" ))));
5861
5862 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5863 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5864
5865 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5866 lShipMidPoint.m_y);
5867 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5868 lShipMidPoint.m_y + 12);
5869}
5870
5871void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5872 wxPoint GPSOffsetPixels,
5873 wxPoint2DDouble lGPSPoint) {
5874 if (m_animationActive) return;
5875 // Develop a uniform length for course predictor line dash length, based on
5876 // physical display size Use this reference length to size all other graphics
5877 // elements
5878 float ref_dim = m_display_size_mm / 24;
5879 ref_dim = wxMin(ref_dim, 12);
5880 ref_dim = wxMax(ref_dim, 6);
5881
5882 wxColour cPred;
5883 cPred.Set(g_cog_predictor_color);
5884 if (cPred == wxNullColour) cPred = PredColor();
5885
5886 // Establish some graphic element line widths dependent on the platform
5887 // display resolution
5888 // double nominal_line_width_pix = wxMax(1.0,
5889 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5890 // not less than 1 pixel
5891 double nominal_line_width_pix = wxMax(
5892 1.0,
5893 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5894
5895 // If the calculated value is greater than the config file spec value, then
5896 // use it.
5897 if (nominal_line_width_pix > g_cog_predictor_width)
5898 g_cog_predictor_width = nominal_line_width_pix;
5899
5900 // Calculate ownship Position Predictor
5901 wxPoint lPredPoint, lHeadPoint;
5902
5903 float pCog = std::isnan(gCog) ? 0 : gCog;
5904 float pSog = std::isnan(gSog) ? 0 : gSog;
5905
5906 double pred_lat, pred_lon;
5907 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5908 &pred_lat, &pred_lon);
5909 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5910
5911 // test to catch the case where COG/HDG line crosses the screen
5912 LLBBox box;
5913
5914 // Should we draw the Head vector?
5915 // Compare the points lHeadPoint and lPredPoint
5916 // If they differ by more than n pixels, and the head vector is valid, then
5917 // render the head vector
5918
5919 float ndelta_pix = 10.;
5920 double hdg_pred_lat, hdg_pred_lon;
5921 bool b_render_hdt = false;
5922 if (!std::isnan(gHdt)) {
5923 // Calculate ownship Heading pointer as a predictor
5924 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5925 &hdg_pred_lon);
5926 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5927 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5928 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5929 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5930 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5931 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5932 }
5933 }
5934
5935 // draw course over ground if they are longer than the ship
5936 wxPoint lShipMidPoint;
5937 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5938 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5939 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5940 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5941
5942 if (lpp >= img_height / 2) {
5943 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5944 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5945 !std::isnan(gSog)) {
5946 // COG Predictor
5947 float dash_length = ref_dim;
5948 wxDash dash_long[2];
5949 dash_long[0] =
5950 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
5951 g_cog_predictor_width); // Long dash , in mm <---------+
5952 dash_long[1] = dash_long[0] / 2.0; // Short gap
5953
5954 // On ultra-hi-res displays, do not allow the dashes to be greater than
5955 // 250, since it is defined as (char)
5956 if (dash_length > 250.) {
5957 dash_long[0] = 250. / g_cog_predictor_width;
5958 dash_long[1] = dash_long[0] / 2;
5959 }
5960
5961 wxPen ppPen2(cPred, g_cog_predictor_width,
5962 (wxPenStyle)g_cog_predictor_style);
5963 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
5964 ppPen2.SetDashes(2, dash_long);
5965 dc.SetPen(ppPen2);
5966 dc.StrokeLine(
5967 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
5968 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
5969
5970 if (g_cog_predictor_width > 1) {
5971 float line_width = g_cog_predictor_width / 3.;
5972
5973 wxDash dash_long3[2];
5974 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
5975 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
5976
5977 wxPen ppPen3(GetGlobalColor(_T ( "UBLCK" )), wxMax(1, line_width),
5978 (wxPenStyle)g_cog_predictor_style);
5979 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
5980 ppPen3.SetDashes(2, dash_long3);
5981 dc.SetPen(ppPen3);
5982 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
5983 lGPSPoint.m_y + GPSOffsetPixels.y,
5984 lPredPoint.x + GPSOffsetPixels.x,
5985 lPredPoint.y + GPSOffsetPixels.y);
5986 }
5987
5988 if (g_cog_predictor_endmarker) {
5989 // Prepare COG predictor endpoint icon
5990 double png_pred_icon_scale_factor = .4;
5991 if (g_ShipScaleFactorExp > 1.0)
5992 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
5993 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
5994
5995 wxPoint icon[4];
5996
5997 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
5998 (float)(lPredPoint.x - lShipMidPoint.x));
5999 cog_rad += (float)PI;
6000
6001 for (int i = 0; i < 4; i++) {
6002 int j = i * 2;
6003 double pxa = (double)(s_png_pred_icon[j]);
6004 double pya = (double)(s_png_pred_icon[j + 1]);
6005
6006 pya *= png_pred_icon_scale_factor;
6007 pxa *= png_pred_icon_scale_factor;
6008
6009 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6010 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6011
6012 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6013 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6014 }
6015
6016 // Render COG endpoint icon
6017 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), g_cog_predictor_width / 2,
6018 wxPENSTYLE_SOLID);
6019 dc.SetPen(ppPen1);
6020 dc.SetBrush(wxBrush(cPred));
6021
6022 dc.StrokePolygon(4, icon);
6023 }
6024 }
6025 }
6026
6027 // HDT Predictor
6028 if (b_render_hdt) {
6029 float hdt_dash_length = ref_dim * 0.4;
6030
6031 cPred.Set(g_ownship_HDTpredictor_color);
6032 if (cPred == wxNullColour) cPred = PredColor();
6033 float hdt_width =
6034 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6035 : g_cog_predictor_width * 0.8);
6036 wxDash dash_short[2];
6037 dash_short[0] =
6038 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6039 hdt_width); // Short dash , in mm <---------+
6040 dash_short[1] =
6041 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6042 hdt_width); // Short gap |
6043
6044 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6045 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6046 ppPen2.SetDashes(2, dash_short);
6047
6048 dc.SetPen(ppPen2);
6049 dc.StrokeLine(
6050 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6051 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6052
6053 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6054 dc.SetPen(ppPen1);
6055 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "GREY2" ))));
6056
6057 if (g_ownship_HDTpredictor_endmarker) {
6058 double nominal_circle_size_pixels = wxMax(
6059 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6060
6061 // Scale the circle to ChartScaleFactor, slightly softened....
6062 if (g_ShipScaleFactorExp > 1.0)
6063 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6064
6065 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6066 lHeadPoint.y + GPSOffsetPixels.y,
6067 nominal_circle_size_pixels / 2);
6068 }
6069 }
6070
6071 // Draw radar rings if activated
6072 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6073 double factor = 1.00;
6074 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6075 factor = 1 / 1.852;
6076 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6077 if (std::isnan(gSog))
6078 factor = 0.0;
6079 else
6080 factor = gSog / 60;
6081 }
6082 factor *= g_fNavAidRadarRingsStep;
6083
6084 double tlat, tlon;
6085 wxPoint r;
6086 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6087 GetCanvasPointPix(tlat, tlon, &r);
6088
6089 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6090 pow((double)(lGPSPoint.m_y - r.y), 2));
6091 int pix_radius = (int)lpp;
6092
6093 extern wxColor GetDimColor(wxColor c);
6094 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6095
6096 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6097
6098 dc.SetPen(ppPen1);
6099 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6100
6101 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6102 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6103 }
6104}
6105
6106void ChartCanvas::ComputeShipScaleFactor(
6107 float icon_hdt, int ownShipWidth, int ownShipLength,
6108 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6109 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6110 float screenResolution = m_pix_per_mm;
6111
6112 // Calculate the true ship length in exact pixels
6113 double ship_bow_lat, ship_bow_lon;
6114 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6115 &ship_bow_lat, &ship_bow_lon);
6116 wxPoint lShipBowPoint;
6117 wxPoint2DDouble b_point =
6118 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6119 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6120
6121 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6122 powf((float)(b_point.m_y - a_point.m_y), 2));
6123
6124 // And in mm
6125 float shipLength_mm = shipLength_px / screenResolution;
6126
6127 // Set minimum ownship drawing size
6128 float ownship_min_mm = g_n_ownship_min_mm;
6129 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6130
6131 // Calculate Nautical Miles distance from midships to gps antenna
6132 float hdt_ant = icon_hdt + 180.;
6133 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6134 float dx = g_n_gps_antenna_offset_x / 1852.;
6135 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6136 {
6137 hdt_ant = icon_hdt;
6138 dy = -dy;
6139 }
6140
6141 // If the drawn ship size is going to be clamped, adjust the gps antenna
6142 // offsets
6143 if (shipLength_mm < ownship_min_mm) {
6144 dy /= shipLength_mm / ownship_min_mm;
6145 dx /= shipLength_mm / ownship_min_mm;
6146 }
6147
6148 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6149
6150 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6151 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6152 &ship_mid_lon1);
6153
6154 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6155 &lShipMidPoint);
6156
6157 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6158 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6159
6160 float scale_factor = shipLength_px / ownShipLength;
6161
6162 // Calculate a scale factor that would produce a reasonably sized icon
6163 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6164
6165 // And choose the correct one
6166 scale_factor = wxMax(scale_factor, scale_factor_min);
6167
6168 scale_factor_y = scale_factor;
6169 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6170 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6171}
6172
6173void ChartCanvas::ShipDraw(ocpnDC &dc) {
6174 if (!GetVP().IsValid()) return;
6175
6176 wxPoint GPSOffsetPixels(0, 0);
6177 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6178
6179 // COG/SOG may be undefined in NMEA data stream
6180 float pCog = std::isnan(gCog) ? 0 : gCog;
6181 float pSog = std::isnan(gSog) ? 0 : gSog;
6182
6183 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6184
6185 lShipMidPoint = lGPSPoint;
6186
6187 // Draw the icon rotated to the COG
6188 // or to the Hdt if available
6189 float icon_hdt = pCog;
6190 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6191
6192 // COG may be undefined in NMEA data stream
6193 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6194
6195 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6196 // predictor
6197 double osd_head_lat, osd_head_lon;
6198 wxPoint osd_head_point;
6199
6200 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6201 &osd_head_lon);
6202
6203 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6204
6205 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6206 (float)(osd_head_point.x - lShipMidPoint.m_x));
6207 icon_rad += (float)PI;
6208
6209 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6210
6211 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6212 // nominal size and is just barely outside the viewport ....
6213 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6214
6215 // TODO: fix to include actual size of boat that will be rendered
6216 int img_height = 0;
6217 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6218 if (GetVP().chart_scale >
6219 300000) // According to S52, this should be 50,000
6220 {
6221 ShipDrawLargeScale(dc, lShipMidPoint);
6222 img_height = 20;
6223 } else {
6224 wxImage pos_image;
6225
6226 // Substitute user ownship image if found
6227 if (m_pos_image_user)
6228 pos_image = m_pos_image_user->Copy();
6229 else if (SHIP_NORMAL == m_ownship_state)
6230 pos_image = m_pos_image_red->Copy();
6231 if (SHIP_LOWACCURACY == m_ownship_state)
6232 pos_image = m_pos_image_yellow->Copy();
6233 else if (SHIP_NORMAL != m_ownship_state)
6234 pos_image = m_pos_image_grey->Copy();
6235
6236 // Substitute user ownship image if found
6237 if (m_pos_image_user) {
6238 pos_image = m_pos_image_user->Copy();
6239
6240 if (SHIP_LOWACCURACY == m_ownship_state)
6241 pos_image = m_pos_image_user_yellow->Copy();
6242 else if (SHIP_NORMAL != m_ownship_state)
6243 pos_image = m_pos_image_user_grey->Copy();
6244 }
6245
6246 img_height = pos_image.GetHeight();
6247
6248 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6249 g_OwnShipIconType > 0) // use large ship
6250 {
6251 int ownShipWidth = 22; // Default values from s_ownship_icon
6252 int ownShipLength = 84;
6253 if (g_OwnShipIconType == 1) {
6254 ownShipWidth = pos_image.GetWidth();
6255 ownShipLength = pos_image.GetHeight();
6256 }
6257
6258 float scale_factor_x, scale_factor_y;
6259 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6260 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6261 scale_factor_x, scale_factor_y);
6262
6263 if (g_OwnShipIconType == 1) { // Scaled bitmap
6264 pos_image.Rescale(ownShipWidth * scale_factor_x,
6265 ownShipLength * scale_factor_y,
6266 wxIMAGE_QUALITY_HIGH);
6267 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6268 wxImage rot_image =
6269 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6270
6271 // Simple sharpening algorithm.....
6272 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6273 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6274 if (rot_image.GetAlpha(ip, jp) > 64)
6275 rot_image.SetAlpha(ip, jp, 255);
6276
6277 wxBitmap os_bm(rot_image);
6278
6279 int w = os_bm.GetWidth();
6280 int h = os_bm.GetHeight();
6281 img_height = h;
6282
6283 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6284 lShipMidPoint.m_y - h / 2, true);
6285
6286 // Maintain dirty box,, missing in __WXMSW__ library
6287 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6288 lShipMidPoint.m_y - h / 2);
6289 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6290 lShipMidPoint.m_y - h / 2 + h);
6291 }
6292
6293 else if (g_OwnShipIconType == 2) { // Scaled Vector
6294 wxPoint ownship_icon[10];
6295
6296 for (int i = 0; i < 10; i++) {
6297 int j = i * 2;
6298 float pxa = (float)(s_ownship_icon[j]);
6299 float pya = (float)(s_ownship_icon[j + 1]);
6300 pya *= scale_factor_y;
6301 pxa *= scale_factor_x;
6302
6303 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6304 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6305
6306 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6307 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6308 }
6309
6310 wxPen ppPen1(GetGlobalColor(_T ( "UBLCK" )), 1, wxPENSTYLE_SOLID);
6311 dc.SetPen(ppPen1);
6312 dc.SetBrush(wxBrush(ShipColor()));
6313
6314 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6315
6316 // draw reference point (midships) cross
6317 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6318 ownship_icon[7].y);
6319 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6320 ownship_icon[9].y);
6321 }
6322
6323 img_height = ownShipLength * scale_factor_y;
6324
6325 // Reference point, where the GPS antenna is
6326 int circle_rad = 3;
6327 if (m_pos_image_user) circle_rad = 1;
6328
6329 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6330 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6331 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6332 } else { // Fixed bitmap icon.
6333 /* non opengl, or suboptimal opengl via ocpndc: */
6334 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6335 wxImage rot_image =
6336 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6337
6338 // Simple sharpening algorithm.....
6339 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6340 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6341 if (rot_image.GetAlpha(ip, jp) > 64)
6342 rot_image.SetAlpha(ip, jp, 255);
6343
6344 wxBitmap os_bm(rot_image);
6345
6346 if (g_ShipScaleFactorExp > 1) {
6347 wxImage scaled_image = os_bm.ConvertToImage();
6348 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6349 1.0; // soften the scale factor a bit
6350 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6351 scaled_image.GetHeight() * factor,
6352 wxIMAGE_QUALITY_HIGH));
6353 }
6354 int w = os_bm.GetWidth();
6355 int h = os_bm.GetHeight();
6356 img_height = h;
6357
6358 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6359 lShipMidPoint.m_y - h / 2, true);
6360
6361 // Reference point, where the GPS antenna is
6362 int circle_rad = 3;
6363 if (m_pos_image_user) circle_rad = 1;
6364
6365 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" )), 1));
6366 dc.SetBrush(wxBrush(GetGlobalColor(_T ( "UIBCK" ))));
6367 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6368
6369 // Maintain dirty box,, missing in __WXMSW__ library
6370 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6371 lShipMidPoint.m_y - h / 2);
6372 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6373 lShipMidPoint.m_y - h / 2 + h);
6374 }
6375 } // ownship draw
6376 }
6377
6378 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6379}
6380
6381/* @ChartCanvas::CalcGridSpacing
6382 **
6383 ** Calculate the major and minor spacing between the lat/lon grid
6384 **
6385 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6386 *window
6387 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6388 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6389 ** @return [void]
6390 */
6391void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6392 float &MinorSpacing) {
6393 // table for calculating the distance between the grids
6394 // [0] view_scale ppm
6395 // [1] spacing between major grid lines in degrees
6396 // [2] spacing between minor grid lines in degrees
6397 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6398 {.000001f, 45.0f, 15.0f},
6399 {.0002f, 30.0f, 10.0f},
6400 {.0003f, 10.0f, 2.0f},
6401 {.0008f, 5.0f, 1.0f},
6402 {.001f, 2.0f, 30.0f / 60.0f},
6403 {.003f, 1.0f, 20.0f / 60.0f},
6404 {.006f, 0.5f, 10.0f / 60.0f},
6405 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6406 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6407 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6408 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6409 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6410 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6411 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6412 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6413
6414 unsigned int tabi;
6415 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6416 if (view_scale_ppm < lltab[tabi][0]) break;
6417 MajorSpacing = lltab[tabi][1]; // major latitude distance
6418 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6419 return;
6420}
6421/* @ChartCanvas::CalcGridText *************************************
6422 **
6423 ** Calculates text to display at the major grid lines
6424 **
6425 ** @param [r] latlon [float] latitude or longitude of grid line
6426 ** @param [r] spacing [float] distance between two major grid lines
6427 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6428 **
6429 ** @return
6430 */
6431
6432wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6433 int deg = (int)fabs(latlon); // degrees
6434 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6435 char postfix;
6436
6437 // calculate postfix letter (NSEW)
6438 if (latlon > 0.0) {
6439 if (bPostfix) {
6440 postfix = 'N';
6441 } else {
6442 postfix = 'E';
6443 }
6444 } else if (latlon < 0.0) {
6445 if (bPostfix) {
6446 postfix = 'S';
6447 } else {
6448 postfix = 'W';
6449 }
6450 } else {
6451 postfix = ' '; // no postfix for equator and greenwich
6452 }
6453 // calculate text, display minutes only if spacing is smaller than one degree
6454
6455 wxString ret;
6456 if (spacing >= 1.0) {
6457 ret.Printf(_T("%3d%c %c"), deg, 0x00b0, postfix);
6458 } else if (spacing >= (1.0 / 60.0)) {
6459 ret.Printf(_T("%3d%c%02.0f %c"), deg, 0x00b0, min, postfix);
6460 } else {
6461 ret.Printf(_T("%3d%c%02.2f %c"), deg, 0x00b0, min, postfix);
6462 }
6463
6464 return ret;
6465}
6466
6467/* @ChartCanvas::GridDraw *****************************************
6468 **
6469 ** Draws major and minor Lat/Lon Grid on the chart
6470 ** - distance between Grid-lm ines are calculated automatic
6471 ** - major grid lines will be across the whole chart window
6472 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6473 **
6474 ** @param [w] dc [wxDC&] the wx drawing context
6475 **
6476 ** @return [void]
6477 ************************************************************************/
6478void ChartCanvas::GridDraw(ocpnDC &dc) {
6479 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6480
6481 double nlat, elon, slat, wlon;
6482 float lat, lon;
6483 float dlon;
6484 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6485 wxCoord w, h;
6486 wxPen GridPen(GetGlobalColor(_T ( "SNDG1" )), 1, wxPENSTYLE_SOLID);
6487 dc.SetPen(GridPen);
6488 dc.SetFont(*m_pgridFont);
6489 dc.SetTextForeground(GetGlobalColor(_T ( "SNDG1" )));
6490
6491 w = m_canvas_width;
6492 h = m_canvas_height;
6493
6494 GetCanvasPixPoint(0, 0, nlat,
6495 wlon); // get lat/lon of upper left point of the window
6496 GetCanvasPixPoint(w, h, slat,
6497 elon); // get lat/lon of lower right point of the window
6498 dlon =
6499 elon -
6500 wlon; // calculate how many degrees of longitude are shown in the window
6501 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6502 {
6503 dlon = dlon + 360.0;
6504 }
6505 // calculate distance between latitude grid lines
6506 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6507
6508 // calculate position of first major latitude grid line
6509 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6510
6511 // Draw Major latitude grid lines and text
6512 while (lat < nlat) {
6513 wxPoint r;
6514 wxString st =
6515 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6516 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6517 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6518 dc.DrawText(st, 0, r.y); // draw text
6519 lat = lat + gridlatMajor;
6520
6521 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6522 }
6523
6524 // calculate position of first minor latitude grid line
6525 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6526
6527 // Draw minor latitude grid lines
6528 while (lat < nlat) {
6529 wxPoint r;
6530 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6531 dc.DrawLine(0, r.y, 10, r.y, false);
6532 dc.DrawLine(w - 10, r.y, w, r.y, false);
6533 lat = lat + gridlatMinor;
6534 }
6535
6536 // calculate distance between grid lines
6537 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6538
6539 // calculate position of first major latitude grid line
6540 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6541
6542 // draw major longitude grid lines
6543 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6544 wxPoint r;
6545 wxString st = CalcGridText(lon, gridlonMajor, false);
6546 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6547 dc.DrawLine(r.x, 0, r.x, h, false);
6548 dc.DrawText(st, r.x, 0);
6549 lon = lon + gridlonMajor;
6550 if (lon > 180.0) {
6551 lon = lon - 360.0;
6552 }
6553
6554 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6555 }
6556
6557 // calculate position of first minor longitude grid line
6558 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6559 // draw minor longitude grid lines
6560 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6561 wxPoint r;
6562 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6563 dc.DrawLine(r.x, 0, r.x, 10, false);
6564 dc.DrawLine(r.x, h - 10, r.x, h, false);
6565 lon = lon + gridlonMinor;
6566 if (lon > 180.0) {
6567 lon = lon - 360.0;
6568 }
6569 }
6570}
6571
6572void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6573 if (0 ) {
6574 double blat, blon, tlat, tlon;
6575 wxPoint r;
6576
6577 int x_origin = m_bDisplayGrid ? 60 : 20;
6578 int y_origin = m_canvas_height - 50;
6579
6580 float dist;
6581 int count;
6582 wxPen pen1, pen2;
6583
6584 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6585 {
6586 dist = 10.0;
6587 count = 5;
6588 pen1 = wxPen(GetGlobalColor(_T ( "SNDG2" )), 3, wxPENSTYLE_SOLID);
6589 pen2 = wxPen(GetGlobalColor(_T ( "SNDG1" )), 3, wxPENSTYLE_SOLID);
6590 } else // Draw 1 mile scale as SCALEB10
6591 {
6592 dist = 1.0;
6593 count = 10;
6594 pen1 = wxPen(GetGlobalColor(_T ( "SCLBR" )), 3, wxPENSTYLE_SOLID);
6595 pen2 = wxPen(GetGlobalColor(_T ( "CHGRD" )), 3, wxPENSTYLE_SOLID);
6596 }
6597
6598 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6599 double rotation = -VPoint.rotation;
6600
6601 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6602 GetCanvasPointPix(tlat, tlon, &r);
6603 int l1 = (y_origin - r.y) / count;
6604
6605 for (int i = 0; i < count; i++) {
6606 int y = l1 * i;
6607 if (i & 1)
6608 dc.SetPen(pen1);
6609 else
6610 dc.SetPen(pen2);
6611
6612 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6613 }
6614 } else {
6615 double blat, blon, tlat, tlon;
6616
6617 int x_origin = 5.0 * GetPixPerMM();
6618 int chartbar_height = GetChartbarHeight();
6619 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6620 // if (style->chartStatusWindowTransparent)
6621 // chartbar_height = 0;
6622 int y_origin = m_canvas_height - chartbar_height - 5;
6623#ifdef __WXOSX__
6624 if (!g_bopengl)
6625 y_origin =
6626 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6627#endif
6628
6629 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6630 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6631
6632 double d;
6633 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6634 d /= 2;
6635
6636 int unit = g_iDistanceFormat;
6637 if (d < .5 &&
6638 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6639 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6640
6641 // nice number
6642 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6643 float places = floor(logdist), rem = logdist - places;
6644 dist = pow(10, places);
6645
6646 if (rem < .2)
6647 dist /= 5;
6648 else if (rem < .5)
6649 dist /= 2;
6650
6651 wxString s = wxString::Format(_T("%g "), dist) + getUsrDistanceUnit(unit);
6652 wxPen pen1 = wxPen(GetGlobalColor(_T ( "UBLCK" )), 3, wxPENSTYLE_SOLID);
6653 double rotation = -VPoint.rotation;
6654
6655 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6656 &tlat, &tlon);
6657 wxPoint r;
6658 GetCanvasPointPix(tlat, tlon, &r);
6659 int l1 = r.x - x_origin;
6660
6661 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6662 12); // Store this for later reference
6663
6664 dc.SetPen(pen1);
6665
6666 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6667 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6668 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6669
6670 dc.SetFont(*m_pgridFont);
6671 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
6672 int w, h;
6673 dc.GetTextExtent(s, &w, &h);
6674 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6675 }
6676}
6677
6678void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6679 // Constants?
6680 double da_min = 2.;
6681 double da_max = 6.;
6682 double ra_min = 0.;
6683 double ra_max = 40.;
6684
6685 wxPen pen_save = dc.GetPen();
6686
6687 wxDateTime now = wxDateTime::Now();
6688
6689 dc.SetPen(pen);
6690
6691 int x0, y0, x1, y1;
6692
6693 x0 = x1 = x + radius; // Start point
6694 y0 = y1 = y;
6695 double angle = 0.;
6696 int i = 0;
6697
6698 while (angle < 360.) {
6699 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6700 angle += da;
6701
6702 if (angle > 360.) angle = 360.;
6703
6704 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6705
6706 double r;
6707 if (i & 1)
6708 r = radius + ra;
6709 else
6710 r = radius - ra;
6711
6712 x1 = (int)(x + cos(angle * PI / 180.) * r);
6713 y1 = (int)(y + sin(angle * PI / 180.) * r);
6714
6715 dc.DrawLine(x0, y0, x1, y1);
6716
6717 x0 = x1;
6718 y0 = y1;
6719
6720 i++;
6721 }
6722
6723 dc.DrawLine(x + radius, y, x1, y1); // closure
6724
6725 dc.SetPen(pen_save);
6726}
6727
6728static bool bAnchorSoundPlaying = false;
6729
6730static void onAnchorSoundFinished(void *ptr) {
6731 g_anchorwatch_sound->UnLoad();
6732 bAnchorSoundPlaying = false;
6733}
6734
6735void ChartCanvas::AlertDraw(ocpnDC &dc) {
6736 // Visual and audio alert for anchorwatch goes here
6737 bool play_sound = false;
6738 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6739 if (AnchorAlertOn1) {
6740 wxPoint TargetPoint;
6741 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
6742 &TargetPoint);
6743 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6744 TargetPoint.y, 100);
6745 play_sound = true;
6746 }
6747 } else
6748 AnchorAlertOn1 = false;
6749
6750 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6751 if (AnchorAlertOn2) {
6752 wxPoint TargetPoint;
6753 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
6754 &TargetPoint);
6755 JaggyCircle(dc, wxPen(GetGlobalColor(_T("URED")), 2), TargetPoint.x,
6756 TargetPoint.y, 100);
6757 play_sound = true;
6758 }
6759 } else
6760 AnchorAlertOn2 = false;
6761
6762 if (play_sound) {
6763 if (!bAnchorSoundPlaying) {
6764 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6765 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6766 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6767 if (g_anchorwatch_sound->IsOk()) {
6768 bAnchorSoundPlaying = true;
6769 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6770 g_anchorwatch_sound->Play();
6771 }
6772 }
6773 }
6774}
6775
6776void ChartCanvas::UpdateShips() {
6777 // Get the rectangle in the current dc which bounds the "ownship" symbol
6778
6779 wxClientDC dc(this);
6780 if (!dc.IsOk()) return;
6781
6782 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6783 if (!test_bitmap.IsOk()) return;
6784
6785 wxMemoryDC temp_dc(test_bitmap);
6786
6787 temp_dc.ResetBoundingBox();
6788 temp_dc.DestroyClippingRegion();
6789 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6790
6791 // Draw the ownship on the temp_dc
6792 ocpnDC ocpndc = ocpnDC(temp_dc);
6793 ShipDraw(ocpndc);
6794
6795 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6796 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6797 if (p) {
6798 wxPoint px;
6799 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6800 ocpndc.CalcBoundingBox(px.x, px.y);
6801 }
6802 }
6803
6804 ship_draw_rect =
6805 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6806 temp_dc.MaxY() - temp_dc.MinY());
6807
6808 wxRect own_ship_update_rect = ship_draw_rect;
6809
6810 if (!own_ship_update_rect.IsEmpty()) {
6811 // The required invalidate rectangle is the union of the last drawn
6812 // rectangle and this drawn rectangle
6813 own_ship_update_rect.Union(ship_draw_last_rect);
6814 own_ship_update_rect.Inflate(2);
6815 }
6816
6817 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6818
6819 ship_draw_last_rect = ship_draw_rect;
6820
6821 temp_dc.SelectObject(wxNullBitmap);
6822}
6823
6824void ChartCanvas::UpdateAlerts() {
6825 // Get the rectangle in the current dc which bounds the detected Alert
6826 // targets
6827
6828 // Use this dc
6829 wxClientDC dc(this);
6830
6831 // Get dc boundary
6832 int sx, sy;
6833 dc.GetSize(&sx, &sy);
6834
6835 // Need a bitmap
6836 wxBitmap test_bitmap(sx, sy, -1);
6837
6838 // Create a memory DC
6839 wxMemoryDC temp_dc;
6840 temp_dc.SelectObject(test_bitmap);
6841
6842 temp_dc.ResetBoundingBox();
6843 temp_dc.DestroyClippingRegion();
6844 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6845
6846 // Draw the Alert Targets on the temp_dc
6847 ocpnDC ocpndc = ocpnDC(temp_dc);
6848 AlertDraw(ocpndc);
6849
6850 // Retrieve the drawing extents
6851 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6852 temp_dc.MaxX() - temp_dc.MinX(),
6853 temp_dc.MaxY() - temp_dc.MinY());
6854
6855 if (!alert_rect.IsEmpty())
6856 alert_rect.Inflate(2); // clear all drawing artifacts
6857
6858 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6859 // The required invalidate rectangle is the union of the last drawn
6860 // rectangle and this drawn rectangle
6861 wxRect alert_update_rect = alert_draw_rect;
6862 alert_update_rect.Union(alert_rect);
6863
6864 // Invalidate the rectangular region
6865 RefreshRect(alert_update_rect, false);
6866 }
6867
6868 // Save this rectangle for next time
6869 alert_draw_rect = alert_rect;
6870
6871 temp_dc.SelectObject(wxNullBitmap); // clean up
6872}
6873
6874void ChartCanvas::UpdateAIS() {
6875 if (!g_pAIS) return;
6876
6877 // Get the rectangle in the current dc which bounds the detected AIS targets
6878
6879 // Use this dc
6880 wxClientDC dc(this);
6881
6882 // Get dc boundary
6883 int sx, sy;
6884 dc.GetSize(&sx, &sy);
6885
6886 wxRect ais_rect;
6887
6888 // How many targets are there?
6889
6890 // If more than "some number", it will be cheaper to refresh the entire
6891 // screen than to build update rectangles for each target.
6892 if (g_pAIS->GetTargetList().size() > 10) {
6893 ais_rect = wxRect(0, 0, sx, sy); // full screen
6894 } else {
6895 // Need a bitmap
6896 wxBitmap test_bitmap(sx, sy, -1);
6897
6898 // Create a memory DC
6899 wxMemoryDC temp_dc;
6900 temp_dc.SelectObject(test_bitmap);
6901
6902 temp_dc.ResetBoundingBox();
6903 temp_dc.DestroyClippingRegion();
6904 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6905
6906 // Draw the AIS Targets on the temp_dc
6907 ocpnDC ocpndc = ocpnDC(temp_dc);
6908 AISDraw(ocpndc, GetVP(), this);
6909 AISDrawAreaNotices(ocpndc, GetVP(), this);
6910
6911 // Retrieve the drawing extents
6912 ais_rect =
6913 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6914 temp_dc.MaxY() - temp_dc.MinY());
6915
6916 if (!ais_rect.IsEmpty())
6917 ais_rect.Inflate(2); // clear all drawing artifacts
6918
6919 temp_dc.SelectObject(wxNullBitmap); // clean up
6920 }
6921
6922 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6923 // The required invalidate rectangle is the union of the last drawn
6924 // rectangle and this drawn rectangle
6925 wxRect ais_update_rect = ais_draw_rect;
6926 ais_update_rect.Union(ais_rect);
6927
6928 // Invalidate the rectangular region
6929 RefreshRect(ais_update_rect, false);
6930 }
6931
6932 // Save this rectangle for next time
6933 ais_draw_rect = ais_rect;
6934}
6935
6936void ChartCanvas::ToggleCPAWarn() {
6937 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6938 wxString mess;
6939 if (g_bCPAWarn) {
6940 g_bTCPA_Max = true;
6941 mess = _("ON");
6942 } else {
6943 g_bTCPA_Max = false;
6944 mess = _("OFF");
6945 }
6946 // Print to status bar if available.
6947 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
6948 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
6949 } else {
6950 if (!g_AisFirstTimeUse) {
6951 OCPNMessageBox(this,
6952 _("CPA Alarm is switched") + _T(" ") + mess.MakeLower(),
6953 _("CPA") + _T(" ") + mess, 4, 4);
6954 }
6955 }
6956}
6957
6958void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
6959
6960void ChartCanvas::OnSize(wxSizeEvent &event) {
6961 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
6962 // GetClientSize returns the size of the canvas area in logical pixels.
6963 GetClientSize(&m_canvas_width, &m_canvas_height);
6964
6965#ifdef __WXOSX__
6966 // Support scaled HDPI displays.
6967 m_displayScale = GetContentScaleFactor();
6968#endif
6969
6970 // Convert to physical pixels.
6971 m_canvas_width *= m_displayScale;
6972 m_canvas_height *= m_displayScale;
6973
6974 // Resize the current viewport
6975 VPoint.pix_width = m_canvas_width;
6976 VPoint.pix_height = m_canvas_height;
6977 VPoint.SetPixelScale(m_displayScale);
6978
6979 // Get some canvas metrics
6980
6981 // Rescale to current value, in order to rebuild VPoint data
6982 // structures for new canvas size
6984
6985 m_absolute_min_scale_ppm =
6986 m_canvas_width /
6987 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
6988
6989 // Inform the parent Frame that I am being resized...
6990 gFrame->ProcessCanvasResize();
6991
6992 // if MUIBar is active, size the bar
6993 // if(g_useMUI && !m_muiBar){ // rebuild if
6994 // necessary
6995 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
6996 // m_muiBarHOSize = m_muiBar->GetSize();
6997 // }
6998
6999 if (m_muiBar) {
7000 SetMUIBarPosition();
7001 UpdateFollowButtonState();
7002 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7003 }
7004
7005 // Set up the scroll margins
7006 xr_margin = m_canvas_width * 95 / 100;
7007 xl_margin = m_canvas_width * 5 / 100;
7008 yt_margin = m_canvas_height * 5 / 100;
7009 yb_margin = m_canvas_height * 95 / 100;
7010
7011 if (m_pQuilt)
7012 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7013
7014 // Resize the scratch BM
7015 delete pscratch_bm;
7016 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7017 m_brepaint_piano = true;
7018
7019 // Resize the Route Calculation BM
7020 m_dc_route.SelectObject(wxNullBitmap);
7021 delete proute_bm;
7022 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7023 m_dc_route.SelectObject(*proute_bm);
7024
7025 // Resize the saved Bitmap
7026 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7027
7028 // Resize the working Bitmap
7029 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7030
7031 // Rescale again, to capture all the changes for new canvas size
7033
7034#ifdef ocpnUSE_GL
7035 if (/*g_bopengl &&*/ m_glcc) {
7036 // FIXME (dave) This can go away?
7037 m_glcc->OnSize(event);
7038 }
7039#endif
7040
7041 FormatPianoKeys();
7042 // Invalidate the whole window
7043 ReloadVP();
7044}
7045
7046void ChartCanvas::ProcessNewGUIScale() {
7047 // m_muiBar->Hide();
7048 delete m_muiBar;
7049 m_muiBar = 0;
7050
7051 CreateMUIBar();
7052}
7053
7054void ChartCanvas::CreateMUIBar() {
7055 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7056
7057 // We need to update the m_bENCGroup flag, at least for the initial creation
7058 // of a MUIBar
7059 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7060
7061 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7062 m_muiBar->SetColorScheme(m_cs);
7063 m_muiBarHOSize = m_muiBar->m_size;
7064 }
7065
7066 if (m_muiBar) {
7067 SetMUIBarPosition();
7068 UpdateFollowButtonState();
7069 m_muiBar->UpdateDynamicValues();
7070 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7071 }
7072}
7073
7074void ChartCanvas::SetMUIBarPosition() {
7075 // if MUIBar is active, size the bar
7076 if (m_muiBar) {
7077 // We estimate the piano width based on the canvas width
7078 int pianoWidth = GetClientSize().x * 0.6f;
7079 // If the piano already exists, we can use its exact width
7080 // if(m_Piano)
7081 // pianoWidth = m_Piano->GetWidth();
7082
7083 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7084 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7085 delete m_muiBar;
7086 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7087 m_muiBar->SetColorScheme(m_cs);
7088 }
7089 }
7090
7091 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7092 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7093 delete m_muiBar;
7094 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7095 m_muiBar->SetColorScheme(m_cs);
7096 }
7097 }
7098
7099 m_muiBar->SetBestPosition();
7100 }
7101}
7102
7103void ChartCanvas::DestroyMuiBar() {
7104 if (m_muiBar) {
7105 delete m_muiBar;
7106 m_muiBar = NULL;
7107 }
7108}
7109
7110void ChartCanvas::ShowCompositeInfoWindow(
7111 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7112 if (n_charts > 0) {
7113 if (NULL == m_pCIWin) {
7114 m_pCIWin = new ChInfoWin(this);
7115 m_pCIWin->Hide();
7116 }
7117
7118 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7119 wxString s;
7120
7121 s = _("Composite of ");
7122
7123 wxString s1;
7124 s1.Printf("%d ", n_charts);
7125 if (n_charts > 1)
7126 s1 += _("charts");
7127 else
7128 s1 += _("chart");
7129 s += s1;
7130 s += '\n';
7131
7132 s1.Printf(_("Chart scale"));
7133 s1 += ": ";
7134 wxString s2;
7135 s2.Printf("1:%d\n", scale);
7136 s += s1;
7137 s += s2;
7138
7139 s1 = _("Zoom in for more information");
7140 s += s1;
7141 s += '\n';
7142
7143 int char_width = s1.Length();
7144 int char_height = 3;
7145
7146 if (g_bChartBarEx) {
7147 s += '\n';
7148 int j = 0;
7149 for (int i : index_vector) {
7150 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7151 wxString path = cte.GetFullSystemPath();
7152 s += path;
7153 s += '\n';
7154 char_height++;
7155 char_width = wxMax(char_width, path.Length());
7156 if (j++ >= 9) break;
7157 }
7158 if (j >= 9) {
7159 s += " .\n .\n .\n";
7160 char_height += 3;
7161 }
7162 s += '\n';
7163 char_height += 1;
7164
7165 char_width += 4; // Fluff
7166 }
7167
7168 m_pCIWin->SetString(s);
7169
7170 m_pCIWin->FitToChars(char_width, char_height);
7171
7172 wxPoint p;
7173 p.x = x / GetContentScaleFactor();
7174 if ((p.x + m_pCIWin->GetWinSize().x) >
7175 (m_canvas_width / GetContentScaleFactor()))
7176 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7177 m_pCIWin->GetWinSize().x) /
7178 2; // centered
7179
7180 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7181 4 - m_pCIWin->GetWinSize().y;
7182
7183 m_pCIWin->dbIndex = 0;
7184 m_pCIWin->chart_scale = 0;
7185 m_pCIWin->SetPosition(p);
7186 m_pCIWin->SetBitmap();
7187 m_pCIWin->Refresh();
7188 m_pCIWin->Show();
7189 }
7190 } else {
7191 HideChartInfoWindow();
7192 }
7193}
7194
7195void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7196 if (dbIndex >= 0) {
7197 if (NULL == m_pCIWin) {
7198 m_pCIWin = new ChInfoWin(this);
7199 m_pCIWin->Hide();
7200 }
7201
7202 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7203 wxString s;
7204 ChartBase *pc = NULL;
7205
7206 // TOCTOU race but worst case will reload chart.
7207 // need to lock it or the background spooler may evict charts in
7208 // OpenChartFromDBAndLock
7209 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7210 pc = ChartData->OpenChartFromDBAndLock(
7211 dbIndex, FULL_INIT); // this must come from cache
7212
7213 int char_width, char_height;
7214 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7215 if (pc) ChartData->UnLockCacheChart(dbIndex);
7216
7217 m_pCIWin->SetString(s);
7218 m_pCIWin->FitToChars(char_width, char_height);
7219
7220 wxPoint p;
7221 p.x = x / GetContentScaleFactor();
7222 if ((p.x + m_pCIWin->GetWinSize().x) >
7223 (m_canvas_width / GetContentScaleFactor()))
7224 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7225 m_pCIWin->GetWinSize().x) /
7226 2; // centered
7227
7228 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7229 4 - m_pCIWin->GetWinSize().y;
7230
7231 m_pCIWin->dbIndex = dbIndex;
7232 m_pCIWin->SetPosition(p);
7233 m_pCIWin->SetBitmap();
7234 m_pCIWin->Refresh();
7235 m_pCIWin->Show();
7236 }
7237 } else {
7238 HideChartInfoWindow();
7239 }
7240}
7241
7242void ChartCanvas::HideChartInfoWindow(void) {
7243 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7244 m_pCIWin->Hide();
7245 m_pCIWin->Destroy();
7246 m_pCIWin = NULL;
7247
7248#ifdef __ANDROID__
7249 androidForceFullRepaint();
7250#endif
7251 }
7252}
7253
7254void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7255 wxMouseEvent ev(wxEVT_MOTION);
7256 ev.m_x = mouse_x;
7257 ev.m_y = mouse_y;
7258 ev.m_leftDown = mouse_leftisdown;
7259
7260 wxEvtHandler *evthp = GetEventHandler();
7261
7262 ::wxPostEvent(evthp, ev);
7263}
7264
7265void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7266 if ((m_panx_target_final - m_panx_target_now) ||
7267 (m_pany_target_final - m_pany_target_now)) {
7268 DoTimedMovementTarget();
7269 } else
7270 DoTimedMovement();
7271}
7272
7273void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7274
7275bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7276 int delta) {
7277 if (m_disable_edge_pan) return false;
7278
7279 bool bft = false;
7280 int pan_margin = m_canvas_width * margin / 100;
7281 int pan_timer_set = 200;
7282 double pan_delta = GetVP().pix_width * delta / 100;
7283 int pan_x = 0;
7284 int pan_y = 0;
7285
7286 if (x > m_canvas_width - pan_margin) {
7287 bft = true;
7288 pan_x = pan_delta;
7289 }
7290
7291 else if (x < pan_margin) {
7292 bft = true;
7293 pan_x = -pan_delta;
7294 }
7295
7296 if (y < pan_margin) {
7297 bft = true;
7298 pan_y = -pan_delta;
7299 }
7300
7301 else if (y > m_canvas_height - pan_margin) {
7302 bft = true;
7303 pan_y = pan_delta;
7304 }
7305
7306 // Of course, if dragging, and the mouse left button is not down, we must
7307 // stop the event injection
7308 if (bdragging) {
7309 if (!g_btouch) {
7310 wxMouseState state = ::wxGetMouseState();
7311#if wxCHECK_VERSION(3, 0, 0)
7312 if (!state.LeftIsDown())
7313#else
7314 if (!state.LeftDown())
7315#endif
7316 bft = false;
7317 }
7318 }
7319 if ((bft) && !pPanTimer->IsRunning()) {
7320 PanCanvas(pan_x, pan_y);
7321 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7322 return true;
7323 }
7324
7325 // This mouse event must not be due to pan timer event injector
7326 // Mouse is out of the pan zone, so prevent any orphan event injection
7327 if ((!bft) && pPanTimer->IsRunning()) {
7328 pPanTimer->Stop();
7329 }
7330
7331 return (false);
7332}
7333
7334// Look for waypoints at the current position.
7335// Used to determine what a mouse event should act on.
7336
7337void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7338 bool setBeingEdited) {
7339 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7340 m_pRoutePointEditTarget = NULL;
7341 m_pFoundPoint = NULL;
7342
7343 SelectItem *pFind = NULL;
7344 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7345 SelectableItemList SelList = pSelect->FindSelectionList(
7346 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7347 wxSelectableItemListNode *node = SelList.GetFirst();
7348 while (node) {
7349 pFind = node->GetData();
7350
7351 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7352
7353 // Get an array of all routes using this point
7354 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7355
7356 // Use route array to determine actual visibility for the point
7357 bool brp_viz = false;
7358 if (m_pEditRouteArray) {
7359 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7360 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7361 if (pr->IsVisible()) {
7362 brp_viz = true;
7363 break;
7364 }
7365 }
7366 } else
7367 brp_viz = frp->IsVisible(); // isolated point
7368
7369 if (brp_viz) {
7370 // Use route array to rubberband all affected routes
7371 if (m_pEditRouteArray) // Editing Waypoint as part of route
7372 {
7373 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7374 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7375 pr->m_bIsBeingEdited = setBeingEdited;
7376 }
7377 m_bRouteEditing = setBeingEdited;
7378 } else // editing Mark
7379 {
7380 frp->m_bRPIsBeingEdited = setBeingEdited;
7381 m_bMarkEditing = setBeingEdited;
7382 }
7383
7384 m_pRoutePointEditTarget = frp;
7385 m_pFoundPoint = pFind;
7386 break; // out of the while(node)
7387 }
7388
7389 node = node->GetNext();
7390 } // while (node)
7391}
7392
7393void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7394 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7395 singleClickEventIsValid = false;
7396 m_DoubleClickTimer->Stop();
7397}
7398
7399bool leftIsDown;
7400
7401bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7402 if (!m_bChartDragging && !m_bDrawingRoute) {
7403 /*
7404 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7405 * mouse event coordinates are in logical pixels.
7406 */
7407 wxRect logicalRect = m_Compass->GetLogicalRect();
7408 bool isInCompass = m_Compass && m_Compass->IsShown() &&
7409 logicalRect.Contains(event.GetPosition());
7410 if (isInCompass) {
7411 if (m_Compass->MouseEvent(event)) {
7412 cursor_region = CENTER;
7413 if (!g_btouch) SetCanvasCursor(event);
7414 return true;
7415 }
7416 }
7417
7418 if (MouseEventToolbar(event)) return true;
7419
7420 if (MouseEventChartBar(event)) return true;
7421
7422 if (MouseEventMUIBar(event)) return true;
7423
7424 if (MouseEventIENCBar(event)) return true;
7425 }
7426 return false;
7427}
7428
7429bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7430 if (!g_bShowChartBar) return false;
7431
7432 if (!m_Piano->MouseEvent(event)) return false;
7433
7434 cursor_region = CENTER;
7435 if (!g_btouch) SetCanvasCursor(event);
7436 return true;
7437}
7438
7439bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7440 if (!IsPrimaryCanvas()) return false;
7441
7442 if (g_MainToolbar) {
7443 if (!g_MainToolbar->MouseEvent(event))
7444 return false;
7445 else
7446 g_MainToolbar->RefreshToolbar();
7447 }
7448
7449 cursor_region = CENTER;
7450 if (!g_btouch) SetCanvasCursor(event);
7451 return true;
7452}
7453
7454bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7455 if (!IsPrimaryCanvas()) return false;
7456
7457 if (g_iENCToolbar) {
7458 if (!g_iENCToolbar->MouseEvent(event))
7459 return false;
7460 else {
7461 g_iENCToolbar->RefreshToolbar();
7462 return true;
7463 }
7464 }
7465 return false;
7466}
7467
7468bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7469 if (m_muiBar) {
7470 if (!m_muiBar->MouseEvent(event)) return false;
7471 }
7472
7473 cursor_region = CENTER;
7474 if (!g_btouch) SetCanvasCursor(event);
7475 if (m_muiBar)
7476 return true;
7477 else
7478 return false;
7479}
7480
7481bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7482 int x, y;
7483
7484 bool bret = false;
7485
7486 event.GetPosition(&x, &y);
7487
7488 x *= m_displayScale;
7489 y *= m_displayScale;
7490
7491 m_MouseDragging = event.Dragging();
7492
7493 // Some systems produce null drag events, where the pointer position has not
7494 // changed from the previous value. Detect this case, and abort further
7495 // processing (FS#1748)
7496#ifdef __WXMSW__
7497 if (event.Dragging()) {
7498 if ((x == mouse_x) && (y == mouse_y)) return true;
7499 }
7500#endif
7501
7502 mouse_x = x;
7503 mouse_y = y;
7504 mouse_leftisdown = event.LeftDown();
7506
7507 // Establish the event region
7508 cursor_region = CENTER;
7509
7510 int chartbar_height = GetChartbarHeight();
7511
7512 if (m_Compass && m_Compass->IsShown() &&
7513 m_Compass->GetRect().Contains(event.GetPosition())) {
7514 cursor_region = CENTER;
7515 } else if (x > xr_margin) {
7516 cursor_region = MID_RIGHT;
7517 } else if (x < xl_margin) {
7518 cursor_region = MID_LEFT;
7519 } else if (y > yb_margin - chartbar_height &&
7520 y < m_canvas_height - chartbar_height) {
7521 cursor_region = MID_TOP;
7522 } else if (y < yt_margin) {
7523 cursor_region = MID_BOT;
7524 } else {
7525 cursor_region = CENTER;
7526 }
7527
7528 if (!g_btouch) SetCanvasCursor(event);
7529
7530 // Protect from leftUp's coming from event handlers in child
7531 // windows who return focus to the canvas.
7532 leftIsDown = event.LeftDown();
7533
7534#ifndef __WXOSX__
7535 if (event.LeftDown()) {
7536 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7537 // The menu bar is temporarily visible due to alt having been pressed.
7538 // Clicking will hide it, and do nothing else.
7539 g_bTempShowMenuBar = false;
7540 parent_frame->ApplyGlobalSettings(false);
7541 return (true);
7542 }
7543 }
7544#endif
7545
7546 // Update modifiers here; some window managers never send the key event
7547 m_modkeys = 0;
7548 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7549 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7550
7551#ifdef __WXMSW__
7552 // TODO Test carefully in other platforms, remove ifdef....
7553 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7554 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7555#endif
7556
7557 event.SetEventObject(this);
7558 if (SendMouseEventToPlugins(event))
7559 return (true); // PlugIn did something, and does not want the canvas to
7560 // do anything else
7561
7562 // Capture LeftUp's and time them, unless it already came from the timer.
7563
7564 // Detect end of chart dragging
7565 if (g_btouch && m_bChartDragging && event.LeftUp()) {
7566 StartChartDragInertia();
7567 }
7568
7569 if (b_handle_dclick && event.LeftUp() && !singleClickEventIsValid) {
7570 // Ignore the second LeftUp after the DClick.
7571 if (m_DoubleClickTimer->IsRunning()) {
7572 m_DoubleClickTimer->Stop();
7573 return (true);
7574 }
7575
7576 // Save the event for later running if there is no DClick.
7577 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7578 singleClickEvent = event;
7579 singleClickEventIsValid = true;
7580 return (true);
7581 }
7582
7583 // This logic is necessary on MSW to handle the case where
7584 // a context (right-click) menu is dismissed without action
7585 // by clicking on the chart surface.
7586 // We need to avoid an unintentional pan by eating some clicks...
7587#ifdef __WXMSW__
7588 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7589 if (g_click_stop > 0) {
7590 g_click_stop--;
7591 return (true);
7592 }
7593 }
7594#endif
7595
7596 // Kick off the Rotation control timer
7597 if (GetUpMode() == COURSE_UP_MODE) {
7598 m_b_rot_hidef = false;
7599 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7600 } else
7601 pRotDefTimer->Stop();
7602
7603 // Retrigger the route leg / AIS target popup timer
7604 bool bRoll = !g_btouch;
7605#ifdef __ANDROID__
7606 bRoll = g_bRollover;
7607#endif
7608 if (bRoll) {
7609 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7610 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7611 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7612 m_RolloverPopupTimer.Start(
7613 10,
7614 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7615 else
7616 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7617 }
7618
7619 // Retrigger the cursor tracking timer
7620 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7621
7622// Show cursor position on Status Bar, if present
7623// except for GTK, under which status bar updates are very slow
7624// due to Update() call.
7625// In this case, as a workaround, update the status window
7626// after an interval timer (pCurTrackTimer) pops, which will happen
7627// whenever the mouse has stopped moving for specified interval.
7628// See the method OnCursorTrackTimerEvent()
7629#if !defined(__WXGTK__) && !defined(__WXQT__)
7630 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7631#endif
7632
7633 // Send the current cursor lat/lon to all PlugIns requesting it
7634 if (g_pi_manager) {
7635 // Occasionally, MSW will produce nonsense events on right click....
7636 // This results in an error in cursor geo position, so we skip this case
7637 if ((x >= 0) && (y >= 0))
7638 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7639 }
7640
7641 if (!g_btouch) {
7642 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7643 wxPoint p = ClientToScreen(wxPoint(x, y));
7644 }
7645 }
7646
7647 if (1 ) {
7648 // Route Creation Rubber Banding
7649 if (m_routeState >= 2) {
7650 r_rband.x = x;
7651 r_rband.y = y;
7652 m_bDrawingRoute = true;
7653
7654 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7655 Refresh(false);
7656 }
7657
7658 // Measure Tool Rubber Banding
7659 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7660 r_rband.x = x;
7661 r_rband.y = y;
7662 m_bDrawingRoute = true;
7663
7664 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7665 Refresh(false);
7666 }
7667 }
7668 return bret;
7669}
7670
7671void ChartCanvas::CallPopupMenu(int x, int y) {
7672 int mx, my;
7673 mx = x;
7674 my = y;
7675
7676 last_drag.x = mx;
7677 last_drag.y = my;
7678 if (m_routeState) { // creating route?
7679 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
7680 return;
7681 }
7682 // General Right Click
7683 // Look for selectable objects
7684 double slat, slon;
7685 slat = m_cursor_lat;
7686 slon = m_cursor_lon;
7687
7688#if defined(__WXMAC__) || defined(__ANDROID__)
7689 wxScreenDC sdc;
7690 ocpnDC dc(sdc);
7691#else
7692 wxClientDC cdc(GetParent());
7693 ocpnDC dc(cdc);
7694#endif
7695
7696 SelectItem *pFindAIS;
7697 SelectItem *pFindRP;
7698 SelectItem *pFindRouteSeg;
7699 SelectItem *pFindTrackSeg;
7700 SelectItem *pFindCurrent = NULL;
7701 SelectItem *pFindTide = NULL;
7702
7703 // Deselect any current objects
7704 if (m_pSelectedRoute) {
7705 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7706 m_pSelectedRoute->DeSelectRoute();
7707#ifdef ocpnUSE_GL
7708 if (g_bopengl && m_glcc) {
7709 InvalidateGL();
7710 Update();
7711 } else
7712#endif
7713 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7714 }
7715
7716 if (m_pFoundRoutePoint) {
7717 m_pFoundRoutePoint->m_bPtIsSelected = false;
7718 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
7719 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
7720 }
7721
7724 if (g_btouch && m_pRoutePointEditTarget) {
7725 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7726 m_pRoutePointEditTarget->m_bPtIsSelected = false;
7727 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7728 }
7729
7730 // Get all the selectable things at the cursor
7731 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7732 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7733 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7734 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7735 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7736
7737 if (m_bShowCurrent)
7738 pFindCurrent =
7739 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7740
7741 if (m_bShowTide) // look for tide stations
7742 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7743
7744 int seltype = 0;
7745
7746 // Try for AIS targets first
7747 if (pFindAIS) {
7748 m_FoundAIS_MMSI = pFindAIS->GetUserData();
7749
7750 // Make sure the target data is available
7751 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
7752 seltype |= SELTYPE_AISTARGET;
7753 }
7754
7755 // Now the various Route Parts
7756
7757 m_pFoundRoutePoint = NULL;
7758 if (pFindRP) {
7759 RoutePoint *pFirstVizPoint = NULL;
7760 RoutePoint *pFoundActiveRoutePoint = NULL;
7761 RoutePoint *pFoundVizRoutePoint = NULL;
7762 Route *pSelectedActiveRoute = NULL;
7763 Route *pSelectedVizRoute = NULL;
7764
7765 // There is at least one routepoint, so get the whole list
7766 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7767 SelectableItemList SelList =
7768 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7769 wxSelectableItemListNode *node = SelList.GetFirst();
7770 while (node) {
7771 SelectItem *pFindSel = node->GetData();
7772
7773 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7774
7775 // Get an array of all routes using this point
7776 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7777
7778 // Use route array (if any) to determine actual visibility for this point
7779 bool brp_viz = false;
7780 if (proute_array) {
7781 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7782 Route *pr = (Route *)proute_array->Item(ir);
7783 if (pr->IsVisible()) {
7784 brp_viz = true;
7785 break;
7786 }
7787 }
7788 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7789 // but still exists as a waypoint
7790 brp_viz = prp->IsVisible(); // so treat as isolated point
7791
7792 } else
7793 brp_viz = prp->IsVisible(); // isolated point
7794
7795 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7796
7797 // Use route array to choose the appropriate route
7798 // Give preference to any active route, otherwise select the first visible
7799 // route in the array for this point
7800 m_pSelectedRoute = NULL;
7801 if (proute_array) {
7802 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7803 Route *pr = (Route *)proute_array->Item(ir);
7804 if (pr->m_bRtIsActive) {
7805 pSelectedActiveRoute = pr;
7806 pFoundActiveRoutePoint = prp;
7807 break;
7808 }
7809 }
7810
7811 if (NULL == pSelectedVizRoute) {
7812 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7813 Route *pr = (Route *)proute_array->Item(ir);
7814 if (pr->IsVisible()) {
7815 pSelectedVizRoute = pr;
7816 pFoundVizRoutePoint = prp;
7817 break;
7818 }
7819 }
7820 }
7821
7822 delete proute_array;
7823 }
7824
7825 node = node->GetNext();
7826 }
7827
7828 // Now choose the "best" selections
7829 if (pFoundActiveRoutePoint) {
7830 m_pFoundRoutePoint = pFoundActiveRoutePoint;
7831 m_pSelectedRoute = pSelectedActiveRoute;
7832 } else if (pFoundVizRoutePoint) {
7833 m_pFoundRoutePoint = pFoundVizRoutePoint;
7834 m_pSelectedRoute = pSelectedVizRoute;
7835 } else
7836 // default is first visible point in list
7837 m_pFoundRoutePoint = pFirstVizPoint;
7838
7839 if (m_pSelectedRoute) {
7840 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7841 } else if (m_pFoundRoutePoint)
7842 seltype |= SELTYPE_MARKPOINT;
7843
7844 // Highlite the selected point, to verify the proper right click
7845 // selection
7846 if (m_pFoundRoutePoint) {
7847 m_pFoundRoutePoint->m_bPtIsSelected = true;
7848 wxRect wp_rect;
7849 RoutePointGui(*m_pFoundRoutePoint)
7850 .CalculateDCRect(m_dc_route, this, &wp_rect);
7851 RefreshRect(wp_rect, true);
7852 }
7853 }
7854
7855 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7856 // routes But call the popup handler with identifier appropriate to the type
7857 if (pFindRouteSeg) // there is at least one select item
7858 {
7859 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7860 SelectableItemList SelList =
7861 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7862
7863 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
7864 {
7865 // Choose the first visible route containing segment in the list
7866 wxSelectableItemListNode *node = SelList.GetFirst();
7867 while (node) {
7868 SelectItem *pFindSel = node->GetData();
7869
7870 Route *pr = (Route *)pFindSel->m_pData3;
7871 if (pr->IsVisible()) {
7872 m_pSelectedRoute = pr;
7873 break;
7874 }
7875 node = node->GetNext();
7876 }
7877 }
7878
7879 if (m_pSelectedRoute) {
7880 if (NULL == m_pFoundRoutePoint)
7881 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7882
7883 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7884 if (m_pSelectedRoute->m_bRtIsSelected) {
7885#ifdef ocpnUSE_GL
7886 if (g_bopengl && m_glcc) {
7887 InvalidateGL();
7888 Update();
7889 } else
7890#endif
7891 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7892 }
7893
7894 seltype |= SELTYPE_ROUTESEGMENT;
7895 }
7896 }
7897
7898 if (pFindTrackSeg) {
7899 m_pSelectedTrack = NULL;
7900 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7901 SelectableItemList SelList =
7902 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7903
7904 // Choose the first visible track containing segment in the list
7905 wxSelectableItemListNode *node = SelList.GetFirst();
7906 while (node) {
7907 SelectItem *pFindSel = node->GetData();
7908
7909 Track *pt = (Track *)pFindSel->m_pData3;
7910 if (pt->IsVisible()) {
7911 m_pSelectedTrack = pt;
7912 break;
7913 }
7914 node = node->GetNext();
7915 }
7916
7917 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7918 }
7919
7920 bool bseltc = false;
7921 // if(0 == seltype)
7922 {
7923 if (pFindCurrent) {
7924 // There may be multiple current entries at the same point.
7925 // For example, there often is a current substation (with directions
7926 // specified) co-located with its master. We want to select the
7927 // substation, so that the direction will be properly indicated on the
7928 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
7929 // substation)
7930 IDX_entry *pIDX_best_candidate;
7931
7932 SelectItem *pFind = NULL;
7933 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7934 SelectableItemList SelList = pSelectTC->FindSelectionList(
7935 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_CURRENTPOINT);
7936
7937 // Default is first entry
7938 wxSelectableItemListNode *node = SelList.GetFirst();
7939 pFind = node->GetData();
7940 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7941
7942 if (SelList.GetCount() > 1) {
7943 node = node->GetNext();
7944 while (node) {
7945 pFind = node->GetData();
7946 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
7947 if (pIDX_candidate->IDX_type == 'c') {
7948 pIDX_best_candidate = pIDX_candidate;
7949 break;
7950 }
7951
7952 node = node->GetNext();
7953 } // while (node)
7954 } else {
7955 wxSelectableItemListNode *node = SelList.GetFirst();
7956 pFind = node->GetData();
7957 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7958 }
7959
7960 m_pIDXCandidate = pIDX_best_candidate;
7961
7962 if (0 == seltype) {
7963 DrawTCWindow(x, y, (void *)pIDX_best_candidate);
7964 Refresh(false);
7965 bseltc = true;
7966 } else
7967 seltype |= SELTYPE_CURRENTPOINT;
7968 }
7969
7970 else if (pFindTide) {
7971 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
7972
7973 if (0 == seltype) {
7974 DrawTCWindow(x, y, (void *)pFindTide->m_pData1);
7975 Refresh(false);
7976 bseltc = true;
7977 } else
7978 seltype |= SELTYPE_TIDEPOINT;
7979 }
7980 }
7981
7982 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7983
7984 if (!bseltc) {
7985 InvokeCanvasMenu(x, y, seltype);
7986
7987 // Clean up if not deleted in InvokeCanvasMenu
7988 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
7989 m_pSelectedRoute->m_bRtIsSelected = false;
7990 }
7991
7992 m_pSelectedRoute = NULL;
7993
7994 if (m_pFoundRoutePoint) {
7995 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
7996 m_pFoundRoutePoint->m_bPtIsSelected = false;
7997 }
7998 m_pFoundRoutePoint = NULL;
7999
8000 Refresh(true);
8001 }
8002
8003 // Seth: Is this refresh needed?
8004 Refresh(false); // needed for MSW, not GTK Why??
8005}
8006bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8007 // For now just bail out completely if the point clicked is not on the chart
8008 if (std::isnan(m_cursor_lat)) return false;
8009
8010 // Mouse Clicks
8011 bool ret = false; // return true if processed
8012
8013 int x, y, mx, my;
8014 event.GetPosition(&x, &y);
8015 mx = x;
8016 my = y;
8017
8018 // Calculate meaningful SelectRadius
8019 float SelectRadius;
8020 SelectRadius = g_Platform->GetSelectRadiusPix() /
8021 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8022
8024 // We start with Double Click processing. The first left click just starts a
8025 // timer and is remembered, then we actually do something if there is a
8026 // LeftDClick. If there is, the two single clicks are ignored.
8027
8028 if (event.LeftDClick() && (cursor_region == CENTER)) {
8029 m_DoubleClickTimer->Start();
8030 singleClickEventIsValid = false;
8031
8032 double zlat, zlon;
8033 GetCanvasPixPoint(x * g_current_monitor_dip_px_ratio,
8034 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8035
8036 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8037 if (m_bShowAIS) {
8038 SelectItem *pFindAIS;
8039 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8040
8041 if (pFindAIS) {
8042 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8043 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8044 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8045 }
8046 return true;
8047 }
8048 }
8049
8050 SelectableItemList rpSelList =
8051 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8052 wxSelectableItemListNode *node = rpSelList.GetFirst();
8053 bool b_onRPtarget = false;
8054 while (node) {
8055 SelectItem *pFind = node->GetData();
8056 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8057 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8058 b_onRPtarget = true;
8059 break;
8060 }
8061 node = node->GetNext();
8062 }
8063
8064 // Double tap with selected RoutePoint or Mark
8065
8066 if (m_pRoutePointEditTarget) {
8067 if (b_onRPtarget) {
8068 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8069 return true;
8070 } else {
8071 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8072 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8073 if (g_btouch)
8074 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8075 wxRect wp_rect;
8076 RoutePointGui(*m_pRoutePointEditTarget)
8077 .CalculateDCRect(m_dc_route, this, &wp_rect);
8078 m_pRoutePointEditTarget = NULL; // cancel selection
8079 RefreshRect(wp_rect, true);
8080 return true;
8081 }
8082 } else {
8083 node = rpSelList.GetFirst();
8084 if (node) {
8085 SelectItem *pFind = node->GetData();
8086 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8087 if (frp) {
8088 wxArrayPtrVoid *proute_array =
8089 g_pRouteMan->GetRouteArrayContaining(frp);
8090
8091 // Use route array (if any) to determine actual visibility for this
8092 // point
8093 bool brp_viz = false;
8094 if (proute_array) {
8095 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8096 Route *pr = (Route *)proute_array->Item(ir);
8097 if (pr->IsVisible()) {
8098 brp_viz = true;
8099 break;
8100 }
8101 }
8102 if (!brp_viz &&
8103 frp->IsShared()) // is not visible as part of route, but still
8104 // exists as a waypoint
8105 brp_viz = frp->IsVisible(); // so treat as isolated point
8106 } else
8107 brp_viz = frp->IsVisible(); // isolated point
8108
8109 if (brp_viz) {
8110 ShowMarkPropertiesDialog(frp);
8111 return true;
8112 }
8113 }
8114 }
8115 }
8116
8117 SelectItem *cursorItem;
8118 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8119
8120 if (cursorItem) {
8121 Route *pr = (Route *)cursorItem->m_pData3;
8122 if (pr->IsVisible()) {
8123 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8124 return true;
8125 }
8126 }
8127
8128 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8129
8130 if (cursorItem) {
8131 Track *pt = (Track *)cursorItem->m_pData3;
8132 if (pt->IsVisible()) {
8133 ShowTrackPropertiesDialog(pt);
8134 return true;
8135 }
8136 }
8137
8138 // Found no object to act on, so show chart info.
8139
8140 ShowObjectQueryWindow(x, y, zlat, zlon);
8141 return true;
8142 }
8143
8145 if (event.LeftDown()) {
8146 // This really should not be needed, but....
8147 // on Windows, when using wxAUIManager, sometimes the focus is lost
8148 // when clicking into another pane, e.g.the AIS target list, and then back
8149 // to this pane. Oddly, some mouse events are not lost, however. Like this
8150 // one....
8151 SetFocus();
8152
8153 last_drag.x = mx;
8154 last_drag.y = my;
8155 leftIsDown = true;
8156
8157 if (!g_btouch) {
8158 if (m_routeState) // creating route?
8159 {
8160 double rlat, rlon;
8161 bool appending = false;
8162 bool inserting = false;
8163 Route *tail = 0;
8164
8165 SetCursor(*pCursorPencil);
8166 rlat = m_cursor_lat;
8167 rlon = m_cursor_lon;
8168
8169 m_bRouteEditing = true;
8170
8171 if (m_routeState == 1) {
8172 m_pMouseRoute = new Route();
8173 pRouteList->Append(m_pMouseRoute);
8174 r_rband.x = x;
8175 r_rband.y = y;
8176 }
8177
8178 // Check to see if there is a nearby point which may be reused
8179 RoutePoint *pMousePoint = NULL;
8180
8181 // Calculate meaningful SelectRadius
8182 double nearby_radius_meters =
8183 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8184
8185 RoutePoint *pNearbyPoint =
8186 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8187 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8188 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8189 wxArrayPtrVoid *proute_array =
8190 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8191
8192 // Use route array (if any) to determine actual visibility for this
8193 // point
8194 bool brp_viz = false;
8195 if (proute_array) {
8196 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8197 Route *pr = (Route *)proute_array->Item(ir);
8198 if (pr->IsVisible()) {
8199 brp_viz = true;
8200 break;
8201 }
8202 }
8203 if (!brp_viz &&
8204 pNearbyPoint->IsShared()) // is not visible as part of route,
8205 // but still exists as a waypoint
8206 brp_viz =
8207 pNearbyPoint->IsVisible(); // so treat as isolated point
8208 } else
8209 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8210
8211 if (brp_viz) {
8212 wxString msg = _("Use nearby waypoint?");
8213 // Don't add a mark without name to the route. Name it if needed
8214 const bool noname(pNearbyPoint->GetName() == "");
8215 if (noname) {
8216 msg =
8217 _("Use nearby nameless waypoint and name it M with"
8218 " a unique number?");
8219 }
8220 // Avoid route finish on focus change for message dialog
8221 m_FinishRouteOnKillFocus = false;
8222 int dlg_return =
8223 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8224 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8225 m_FinishRouteOnKillFocus = true;
8226 if (dlg_return == wxID_YES) {
8227 if (noname) {
8228 if (m_pMouseRoute) {
8229 int last_wp_num = m_pMouseRoute->GetnPoints();
8230 // AP-ECRMB will truncate to 6 characters
8231 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8232 wxString wp_name = wxString::Format(
8233 "M%002i-%s", last_wp_num + 1, guid_short);
8234 pNearbyPoint->SetName(wp_name);
8235 } else
8236 pNearbyPoint->SetName("WPXX");
8237 }
8238 pMousePoint = pNearbyPoint;
8239
8240 // Using existing waypoint, so nothing to delete for undo.
8241 if (m_routeState > 1)
8242 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8243 Undo_HasParent, NULL);
8244
8245 tail =
8246 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8247 bool procede = false;
8248 if (tail) {
8249 procede = true;
8250 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8251 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8252 procede = false;
8253 }
8254
8255 if (procede) {
8256 int dlg_return;
8257 m_FinishRouteOnKillFocus = false;
8258 if (m_routeState ==
8259 1) { // first point in new route, preceeding route to be
8260 // added? Not touch case
8261
8262 wxString dmsg =
8263 _("Insert first part of this route in the new route?");
8264 if (tail->GetIndexOf(pMousePoint) ==
8265 tail->GetnPoints()) // Starting on last point of another
8266 // route?
8267 dmsg = _("Insert this route in the new route?");
8268
8269 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8270 dlg_return = OCPNMessageBox(
8271 this, dmsg, _("OpenCPN Route Create"),
8272 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8273 m_FinishRouteOnKillFocus = true;
8274
8275 if (dlg_return == wxID_YES) {
8276 inserting = true; // part of the other route will be
8277 // preceeding the new route
8278 }
8279 }
8280 } else {
8281 wxString dmsg =
8282 _("Append last part of this route to the new route?");
8283 if (tail->GetIndexOf(pMousePoint) == 1)
8284 dmsg = _(
8285 "Append this route to the new route?"); // Picking the
8286 // first point
8287 // of another
8288 // route?
8289
8290 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8291 dlg_return = OCPNMessageBox(
8292 this, dmsg, _("OpenCPN Route Create"),
8293 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8294 m_FinishRouteOnKillFocus = true;
8295
8296 if (dlg_return == wxID_YES) {
8297 appending = true; // part of the other route will be
8298 // appended to the new route
8299 }
8300 }
8301 }
8302 }
8303
8304 // check all other routes to see if this point appears in any
8305 // other route If it appears in NO other route, then it should e
8306 // considered an isolated mark
8307 if (!FindRouteContainingWaypoint(pMousePoint))
8308 pMousePoint->SetShared(true);
8309 }
8310 }
8311 }
8312
8313 if (NULL == pMousePoint) { // need a new point
8314 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8315 _T(""), wxEmptyString);
8316 pMousePoint->SetNameShown(false);
8317
8318 pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8319 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8320
8321 if (m_routeState > 1)
8322 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8323 Undo_IsOrphanded, NULL);
8324 }
8325
8326 if (m_pMouseRoute) {
8327 if (m_routeState == 1) {
8328 // First point in the route.
8329 m_pMouseRoute->AddPoint(pMousePoint);
8330 } else {
8331 if (m_pMouseRoute->m_NextLegGreatCircle) {
8332 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8333 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8334 &rhumbBearing, &rhumbDist);
8335 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8336 rlat, &gcDist, &gcBearing, NULL);
8337 double gcDistNM = gcDist / 1852.0;
8338
8339 // Empirically found expression to get reasonable route segments.
8340 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8341 pow(rhumbDist - gcDistNM - 1, 0.5);
8342
8343 wxString msg;
8344 msg << _("For this leg the Great Circle route is ")
8345 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8346 << _(" shorter than rhumbline.\n\n")
8347 << _("Would you like include the Great Circle routing points "
8348 "for this leg?");
8349
8350 m_FinishRouteOnKillFocus = false;
8351 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8352 // does not fully capture mouse
8353
8354 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8355 wxYES_NO | wxNO_DEFAULT);
8356
8357 m_disable_edge_pan = false;
8358 m_FinishRouteOnKillFocus = true;
8359
8360 if (answer == wxID_YES) {
8361 RoutePoint *gcPoint;
8362 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8363 wxRealPoint gcCoord;
8364
8365 for (int i = 1; i <= segmentCount; i++) {
8366 double fraction = (double)i * (1.0 / (double)segmentCount);
8367 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8368 gcDist * fraction, gcBearing,
8369 &gcCoord.x, &gcCoord.y, NULL);
8370
8371 if (i < segmentCount) {
8372 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
8373 _T(""), wxEmptyString);
8374 gcPoint->SetNameShown(false);
8375 pConfig->AddNewWayPoint(gcPoint, -1);
8376 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8377 gcPoint);
8378 } else {
8379 gcPoint = pMousePoint; // Last point, previously exsisting!
8380 }
8381
8382 m_pMouseRoute->AddPoint(gcPoint);
8383 pSelect->AddSelectableRouteSegment(
8384 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8385 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8386 prevGcPoint = gcPoint;
8387 }
8388
8389 undo->CancelUndoableAction(true);
8390
8391 } else {
8392 m_pMouseRoute->AddPoint(pMousePoint);
8393 pSelect->AddSelectableRouteSegment(
8394 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8395 pMousePoint, m_pMouseRoute);
8396 undo->AfterUndoableAction(m_pMouseRoute);
8397 }
8398 } else {
8399 // Ordinary rhumblinesegment.
8400 m_pMouseRoute->AddPoint(pMousePoint);
8401 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8402 rlon, m_prev_pMousePoint,
8403 pMousePoint, m_pMouseRoute);
8404 undo->AfterUndoableAction(m_pMouseRoute);
8405 }
8406 }
8407 }
8408 m_prev_rlat = rlat;
8409 m_prev_rlon = rlon;
8410 m_prev_pMousePoint = pMousePoint;
8411 if (m_pMouseRoute)
8412 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8413
8414 m_routeState++;
8415
8416 if (appending ||
8417 inserting) { // Appending a route or making a new route
8418 int connect = tail->GetIndexOf(pMousePoint);
8419 if (connect == 1) {
8420 inserting = false; // there is nothing to insert
8421 appending = true; // so append
8422 }
8423 int length = tail->GetnPoints();
8424
8425 int i;
8426 int start, stop;
8427 if (appending) {
8428 start = connect + 1;
8429 stop = length;
8430 } else { // inserting
8431 start = 1;
8432 stop = connect;
8433 m_pMouseRoute->RemovePoint(
8434 m_pMouseRoute
8435 ->GetLastPoint()); // Remove the first and only point
8436 }
8437 for (i = start; i <= stop; i++) {
8438 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8439 if (m_pMouseRoute)
8440 m_pMouseRoute->m_lastMousePointIndex =
8441 m_pMouseRoute->GetnPoints();
8442 m_routeState++;
8443 gFrame->RefreshAllCanvas();
8444 ret = true;
8445 }
8446 m_prev_rlat =
8447 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8448 m_prev_rlon =
8449 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8450 m_pMouseRoute->FinalizeForRendering();
8451 }
8452 gFrame->RefreshAllCanvas();
8453 ret = true;
8454 }
8455
8456 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8457 {
8458 SetCursor(*pCursorPencil);
8459
8460 if (!m_pMeasureRoute) {
8461 m_pMeasureRoute = new Route();
8462 pRouteList->Append(m_pMeasureRoute);
8463 }
8464
8465 if (m_nMeasureState == 1) {
8466 r_rband.x = x;
8467 r_rband.y = y;
8468 }
8469
8470 RoutePoint *pMousePoint = new RoutePoint(m_cursor_lat, m_cursor_lon,
8471 wxString(_T ( "circle" )),
8472 wxEmptyString, wxEmptyString);
8473 pMousePoint->m_bShowName = false;
8474 pMousePoint->SetShowWaypointRangeRings(false);
8475
8476 m_pMeasureRoute->AddPoint(pMousePoint);
8477
8478 m_prev_rlat = m_cursor_lat;
8479 m_prev_rlon = m_cursor_lon;
8480 m_prev_pMousePoint = pMousePoint;
8481 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8482
8483 m_nMeasureState++;
8484 gFrame->RefreshAllCanvas();
8485 ret = true;
8486 }
8487
8488 else {
8489 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8490 }
8491 } // !g_btouch
8492 else { // g_btouch
8493
8494 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8495 // if near screen edge, pan with injection
8496 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8497 // return;
8498 // }
8499 }
8500 }
8501
8502 if (ret) return true;
8503 }
8504
8505 if (event.Dragging()) {
8506 // in touch screen mode ensure the finger/cursor is on the selected point's
8507 // radius to allow dragging
8508 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8509 if (g_btouch) {
8510 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8511 SelectItem *pFind = NULL;
8512 SelectableItemList SelList = pSelect->FindSelectionList(
8513 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8514 wxSelectableItemListNode *node = SelList.GetFirst();
8515 while (node) {
8516 pFind = node->GetData();
8517 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8518 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8519 node = node->GetNext();
8520 }
8521 }
8522
8523 // Check for use of dragHandle
8524 if (m_pRoutePointEditTarget &&
8525 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8526 SelectItem *pFind = NULL;
8527 SelectableItemList SelList = pSelect->FindSelectionList(
8528 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8529 wxSelectableItemListNode *node = SelList.GetFirst();
8530 while (node) {
8531 pFind = node->GetData();
8532 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8533 if (m_pRoutePointEditTarget == frp) {
8534 m_bIsInRadius = true;
8535 break;
8536 }
8537 node = node->GetNext();
8538 }
8539
8540 if (!m_dragoffsetSet) {
8541 RoutePointGui(*m_pRoutePointEditTarget)
8542 .PresetDragOffset(this, mouse_x, mouse_y);
8543 m_dragoffsetSet = true;
8544 }
8545 }
8546 }
8547
8548 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8549 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8550
8551 if (NULL == g_pMarkInfoDialog) {
8552 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8553 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8554 DraggingAllowed = false;
8555
8556 if (m_pRoutePointEditTarget &&
8557 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8558 DraggingAllowed = false;
8559
8560 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8561
8562 if (DraggingAllowed) {
8563 if (!undo->InUndoableAction()) {
8564 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8565 Undo_NeedsCopy, m_pFoundPoint);
8566 }
8567
8568 // Get the update rectangle for the union of the un-edited routes
8569 wxRect pre_rect;
8570
8571 if (!g_bopengl && m_pEditRouteArray) {
8572 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8573 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8574 // Need to validate route pointer
8575 // Route may be gone due to drgging close to ownship with
8576 // "Delete On Arrival" state set, as in the case of
8577 // navigating to an isolated waypoint on a temporary route
8578 if (g_pRouteMan->IsRouteValid(pr)) {
8579 wxRect route_rect;
8580 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8581 pre_rect.Union(route_rect);
8582 }
8583 }
8584 }
8585
8586 double new_cursor_lat = m_cursor_lat;
8587 double new_cursor_lon = m_cursor_lon;
8588
8589 if (CheckEdgePan(x, y, true, 5, 2))
8590 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8591
8592 // update the point itself
8593 if (g_btouch) {
8594 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8595 // new_cursor_lat, new_cursor_lon);
8596 RoutePointGui(*m_pRoutePointEditTarget)
8597 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8598 // update the Drag Handle entry in the pSelect list
8599 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8600 m_pRoutePointEditTarget,
8601 SELTYPE_DRAGHANDLE);
8602 m_pFoundPoint->m_slat =
8603 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8604 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8605 } else {
8606 m_pRoutePointEditTarget->m_lat =
8607 new_cursor_lat; // update the RoutePoint entry
8608 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8609 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8610 m_pFoundPoint->m_slat =
8611 new_cursor_lat; // update the SelectList entry
8612 m_pFoundPoint->m_slon = new_cursor_lon;
8613 }
8614
8615 // Update the MarkProperties Dialog, if currently shown
8616 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8617 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8618 g_pMarkInfoDialog->UpdateProperties(true);
8619 }
8620
8621 if (g_bopengl) {
8622 // InvalidateGL();
8623 Refresh(false);
8624 } else {
8625 // Get the update rectangle for the edited route
8626 wxRect post_rect;
8627
8628 if (m_pEditRouteArray) {
8629 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8630 ir++) {
8631 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8632 if (g_pRouteMan->IsRouteValid(pr)) {
8633 wxRect route_rect;
8634 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8635 post_rect.Union(route_rect);
8636 }
8637 }
8638 }
8639
8640 // Invalidate the union region
8641 pre_rect.Union(post_rect);
8642 RefreshRect(pre_rect, false);
8643 }
8644 gFrame->RefreshCanvasOther(this);
8645 m_bRoutePoinDragging = true;
8646 }
8647 ret = true;
8648 } // if Route Editing
8649
8650 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8651 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8652
8653 if (NULL == g_pMarkInfoDialog) {
8654 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8655 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8656 DraggingAllowed = false;
8657
8658 if (m_pRoutePointEditTarget &&
8659 (m_pRoutePointEditTarget->GetIconName() == _T("mob")))
8660 DraggingAllowed = false;
8661
8662 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8663
8664 if (DraggingAllowed) {
8665 if (!undo->InUndoableAction()) {
8666 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8667 Undo_NeedsCopy, m_pFoundPoint);
8668 }
8669
8670 // The mark may be an anchorwatch
8671 double lpp1 = 0.;
8672 double lpp2 = 0.;
8673 double lppmax;
8674
8675 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8676 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8677 }
8678 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8679 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8680 }
8681 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8682
8683 // Get the update rectangle for the un-edited mark
8684 wxRect pre_rect;
8685 if (!g_bopengl) {
8686 RoutePointGui(*m_pRoutePointEditTarget)
8687 .CalculateDCRect(m_dc_route, this, &pre_rect);
8688 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8689 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8690 (int)(lppmax - (pre_rect.height / 2)));
8691 }
8692
8693 // update the point itself
8694 if (g_btouch) {
8695 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8696 // m_cursor_lat, m_cursor_lon);
8697 RoutePointGui(*m_pRoutePointEditTarget)
8698 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8699 // update the Drag Handle entry in the pSelect list
8700 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
8701 m_pRoutePointEditTarget,
8702 SELTYPE_DRAGHANDLE);
8703 m_pFoundPoint->m_slat =
8704 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8705 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8706 } else {
8707 m_pRoutePointEditTarget->m_lat =
8708 m_cursor_lat; // update the RoutePoint entry
8709 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
8710 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8711 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
8712 m_pFoundPoint->m_slon = m_cursor_lon;
8713 }
8714
8715 // Update the MarkProperties Dialog, if currently shown
8716 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8717 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8718 g_pMarkInfoDialog->UpdateProperties(true);
8719 }
8720
8721 // Invalidate the union region
8722 if (g_bopengl) {
8723 if (!g_btouch) InvalidateGL();
8724 Refresh(false);
8725 } else {
8726 // Get the update rectangle for the edited mark
8727 wxRect post_rect;
8728 RoutePointGui(*m_pRoutePointEditTarget)
8729 .CalculateDCRect(m_dc_route, this, &post_rect);
8730 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
8731 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
8732 (int)(lppmax - (post_rect.height / 2)));
8733
8734 // Invalidate the union region
8735 pre_rect.Union(post_rect);
8736 RefreshRect(pre_rect, false);
8737 }
8738 gFrame->RefreshCanvasOther(this);
8739 m_bRoutePoinDragging = true;
8740 }
8741 ret = true;
8742 }
8743
8744 if (ret) return true;
8745 } // dragging
8746
8747 if (event.LeftUp()) {
8748 bool b_startedit_route = false;
8749 m_dragoffsetSet = false;
8750
8751 if (g_btouch) {
8752 m_bChartDragging = false;
8753 m_bIsInRadius = false;
8754
8755 if (m_routeState) // creating route?
8756 {
8757 if (m_bedge_pan) {
8758 m_bedge_pan = false;
8759 return false;
8760 }
8761
8762 double rlat, rlon;
8763 bool appending = false;
8764 bool inserting = false;
8765 Route *tail = 0;
8766
8767 rlat = m_cursor_lat;
8768 rlon = m_cursor_lon;
8769
8770 if (m_pRoutePointEditTarget) {
8771 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8772 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8773 if (!g_bopengl) {
8774 wxRect wp_rect;
8775 RoutePointGui(*m_pRoutePointEditTarget)
8776 .CalculateDCRect(m_dc_route, this, &wp_rect);
8777 RefreshRect(wp_rect, true);
8778 }
8779 m_pRoutePointEditTarget = NULL;
8780 }
8781 m_bRouteEditing = true;
8782
8783 if (m_routeState == 1) {
8784 m_pMouseRoute = new Route();
8785 m_pMouseRoute->SetHiLite(50);
8786 pRouteList->Append(m_pMouseRoute);
8787 r_rband.x = x;
8788 r_rband.y = y;
8789 }
8790
8791 // Check to see if there is a nearby point which may be reused
8792 RoutePoint *pMousePoint = NULL;
8793
8794 // Calculate meaningful SelectRadius
8795 double nearby_radius_meters =
8796 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8797
8798 RoutePoint *pNearbyPoint =
8799 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8800 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8801 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8802 int dlg_return;
8803#ifndef __WXOSX__
8804 m_FinishRouteOnKillFocus =
8805 false; // Avoid route finish on focus change for message dialog
8806 dlg_return = OCPNMessageBox(
8807 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
8808 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8809 m_FinishRouteOnKillFocus = true;
8810#else
8811 dlg_return = wxID_YES;
8812#endif
8813 if (dlg_return == wxID_YES) {
8814 pMousePoint = pNearbyPoint;
8815
8816 // Using existing waypoint, so nothing to delete for undo.
8817 if (m_routeState > 1)
8818 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8819 Undo_HasParent, NULL);
8820 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8821
8822 bool procede = false;
8823 if (tail) {
8824 procede = true;
8825 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8826 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8827 procede = false;
8828 }
8829
8830 if (procede) {
8831 int dlg_return;
8832 m_FinishRouteOnKillFocus = false;
8833 if (m_routeState == 1) { // first point in new route, preceeding
8834 // route to be added? touch case
8835
8836 wxString dmsg =
8837 _("Insert first part of this route in the new route?");
8838 if (tail->GetIndexOf(pMousePoint) ==
8839 tail->GetnPoints()) // Starting on last point of another
8840 // route?
8841 dmsg = _("Insert this route in the new route?");
8842
8843 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8844 dlg_return =
8845 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8846 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8847 m_FinishRouteOnKillFocus = true;
8848
8849 if (dlg_return == wxID_YES) {
8850 inserting = true; // part of the other route will be
8851 // preceeding the new route
8852 }
8853 }
8854 } else {
8855 wxString dmsg =
8856 _("Append last part of this route to the new route?");
8857 if (tail->GetIndexOf(pMousePoint) == 1)
8858 dmsg = _(
8859 "Append this route to the new route?"); // Picking the
8860 // first point of
8861 // another route?
8862
8863 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8864 dlg_return =
8865 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
8866 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8867 m_FinishRouteOnKillFocus = true;
8868
8869 if (dlg_return == wxID_YES) {
8870 appending = true; // part of the other route will be
8871 // appended to the new route
8872 }
8873 }
8874 }
8875 }
8876
8877 // check all other routes to see if this point appears in any other
8878 // route If it appears in NO other route, then it should e
8879 // considered an isolated mark
8880 if (!FindRouteContainingWaypoint(pMousePoint))
8881 pMousePoint->SetShared(true);
8882 }
8883 }
8884
8885 if (NULL == pMousePoint) { // need a new point
8886 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8887 _T(""), wxEmptyString);
8888 pMousePoint->SetNameShown(false);
8889
8890 pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8891 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8892
8893 if (m_routeState > 1)
8894 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8895 Undo_IsOrphanded, NULL);
8896 }
8897
8898 if (m_routeState == 1) {
8899 // First point in the route.
8900 m_pMouseRoute->AddPoint(pMousePoint);
8901 } else {
8902 if (m_pMouseRoute->m_NextLegGreatCircle) {
8903 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8904 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8905 &rhumbBearing, &rhumbDist);
8906 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
8907 &gcDist, &gcBearing, NULL);
8908 double gcDistNM = gcDist / 1852.0;
8909
8910 // Empirically found expression to get reasonable route segments.
8911 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8912 pow(rhumbDist - gcDistNM - 1, 0.5);
8913
8914 wxString msg;
8915 msg << _("For this leg the Great Circle route is ")
8916 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8917 << _(" shorter than rhumbline.\n\n")
8918 << _("Would you like include the Great Circle routing points "
8919 "for this leg?");
8920
8921#ifndef __WXOSX__
8922 m_FinishRouteOnKillFocus = false;
8923 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8924 wxYES_NO | wxNO_DEFAULT);
8925 m_FinishRouteOnKillFocus = true;
8926#else
8927 int answer = wxID_NO;
8928#endif
8929
8930 if (answer == wxID_YES) {
8931 RoutePoint *gcPoint;
8932 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8933 wxRealPoint gcCoord;
8934
8935 for (int i = 1; i <= segmentCount; i++) {
8936 double fraction = (double)i * (1.0 / (double)segmentCount);
8937 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8938 gcDist * fraction, gcBearing,
8939 &gcCoord.x, &gcCoord.y, NULL);
8940
8941 if (i < segmentCount) {
8942 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, _T("xmblue"),
8943 _T(""), wxEmptyString);
8944 gcPoint->SetNameShown(false);
8945 pConfig->AddNewWayPoint(gcPoint, -1);
8946 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8947 gcPoint);
8948 } else {
8949 gcPoint = pMousePoint; // Last point, previously exsisting!
8950 }
8951
8952 m_pMouseRoute->AddPoint(gcPoint);
8953 pSelect->AddSelectableRouteSegment(
8954 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8955 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8956 prevGcPoint = gcPoint;
8957 }
8958
8959 undo->CancelUndoableAction(true);
8960
8961 } else {
8962 m_pMouseRoute->AddPoint(pMousePoint);
8963 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8964 rlon, m_prev_pMousePoint,
8965 pMousePoint, m_pMouseRoute);
8966 undo->AfterUndoableAction(m_pMouseRoute);
8967 }
8968 } else {
8969 // Ordinary rhumblinesegment.
8970 m_pMouseRoute->AddPoint(pMousePoint);
8971 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8972 rlon, m_prev_pMousePoint,
8973 pMousePoint, m_pMouseRoute);
8974 undo->AfterUndoableAction(m_pMouseRoute);
8975 }
8976 }
8977
8978 m_prev_rlat = rlat;
8979 m_prev_rlon = rlon;
8980 m_prev_pMousePoint = pMousePoint;
8981 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8982
8983 m_routeState++;
8984
8985 if (appending ||
8986 inserting) { // Appending a route or making a new route
8987 int connect = tail->GetIndexOf(pMousePoint);
8988 if (connect == 1) {
8989 inserting = false; // there is nothing to insert
8990 appending = true; // so append
8991 }
8992 int length = tail->GetnPoints();
8993
8994 int i;
8995 int start, stop;
8996 if (appending) {
8997 start = connect + 1;
8998 stop = length;
8999 } else { // inserting
9000 start = 1;
9001 stop = connect;
9002 m_pMouseRoute->RemovePoint(
9003 m_pMouseRoute
9004 ->GetLastPoint()); // Remove the first and only point
9005 }
9006 for (i = start; i <= stop; i++) {
9007 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9008 if (m_pMouseRoute)
9009 m_pMouseRoute->m_lastMousePointIndex =
9010 m_pMouseRoute->GetnPoints();
9011 m_routeState++;
9012 gFrame->RefreshAllCanvas();
9013 ret = true;
9014 }
9015 m_prev_rlat =
9016 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9017 m_prev_rlon =
9018 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9019 m_pMouseRoute->FinalizeForRendering();
9020 }
9021
9022 Refresh(true);
9023 ret = true;
9024 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9025 {
9026 if (m_bedge_pan) {
9027 m_bedge_pan = false;
9028 return false;
9029 }
9030
9031 if (m_nMeasureState == 1) {
9032 m_pMeasureRoute = new Route();
9033 pRouteList->Append(m_pMeasureRoute);
9034 r_rband.x = x;
9035 r_rband.y = y;
9036 }
9037
9038 if (m_pMeasureRoute) {
9039 RoutePoint *pMousePoint = new RoutePoint(
9040 m_cursor_lat, m_cursor_lon, wxString(_T ( "circle" )),
9041 wxEmptyString, wxEmptyString);
9042 pMousePoint->m_bShowName = false;
9043
9044 m_pMeasureRoute->AddPoint(pMousePoint);
9045
9046 m_prev_rlat = m_cursor_lat;
9047 m_prev_rlon = m_cursor_lon;
9048 m_prev_pMousePoint = pMousePoint;
9049 m_pMeasureRoute->m_lastMousePointIndex =
9050 m_pMeasureRoute->GetnPoints();
9051
9052 m_nMeasureState++;
9053 } else {
9054 CancelMeasureRoute();
9055 }
9056
9057 Refresh(true);
9058 ret = true;
9059 } else {
9060 bool bSelectAllowed = true;
9061 if (NULL == g_pMarkInfoDialog) {
9062 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9063 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9064 bSelectAllowed = false;
9065
9066 /*if this left up happens at the end of a route point dragging and if
9067 the cursor/thumb is on the draghandle icon, not on the point iself a new
9068 selection will select nothing and the drag will never be ended, so the
9069 legs around this point never selectable. At this step we don't need a
9070 new selection, just keep the previoulsly selected and dragged point */
9071 if (m_bRoutePoinDragging) bSelectAllowed = false;
9072
9073 if (bSelectAllowed) {
9074 bool b_was_editing_mark = m_bMarkEditing;
9075 bool b_was_editing_route = m_bRouteEditing;
9076 FindRoutePointsAtCursor(SelectRadius,
9077 true); // Possibly selecting a point in a
9078 // route for later dragging
9079
9080 /*route and a mark points in layer can't be dragged so should't be
9081 * selected and no draghandle icon*/
9082 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9083 m_pRoutePointEditTarget = NULL;
9084
9085 if (!b_was_editing_route) {
9086 if (m_pEditRouteArray) {
9087 b_startedit_route = true;
9088
9089 // Hide the track and route rollover during route point edit, not
9090 // needed, and may be confusing
9091 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9092 m_pTrackRolloverWin->IsActive(false);
9093 }
9094 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9095 m_pRouteRolloverWin->IsActive(false);
9096 }
9097
9098 wxRect pre_rect;
9099 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9100 ir++) {
9101 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9102 // Need to validate route pointer
9103 // Route may be gone due to drgging close to ownship with
9104 // "Delete On Arrival" state set, as in the case of
9105 // navigating to an isolated waypoint on a temporary route
9106 if (g_pRouteMan->IsRouteValid(pr)) {
9107 // pr->SetHiLite(50);
9108 wxRect route_rect;
9109 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9110 pre_rect.Union(route_rect);
9111 }
9112 }
9113 RefreshRect(pre_rect, true);
9114 }
9115 } else {
9116 b_startedit_route = false;
9117 }
9118
9119 // Mark editing
9120 if (m_pRoutePointEditTarget) {
9121 if (b_was_editing_mark ||
9122 b_was_editing_route) { // kill previous hilight
9123 if (m_lastRoutePointEditTarget) {
9124 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9125 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9126 RoutePointGui(*m_lastRoutePointEditTarget)
9127 .EnableDragHandle(false);
9128 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9129 SELTYPE_DRAGHANDLE);
9130 }
9131 }
9132
9133 if (m_pRoutePointEditTarget) {
9134 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9135 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9136 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9137 wxPoint2DDouble dragHandlePoint =
9138 RoutePointGui(*m_pRoutePointEditTarget)
9139 .GetDragHandlePoint(this);
9140 pSelect->AddSelectablePoint(
9141 dragHandlePoint.m_y, dragHandlePoint.m_x,
9142 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9143 }
9144 } else { // Deselect everything
9145 if (m_lastRoutePointEditTarget) {
9146 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9147 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9148 RoutePointGui(*m_lastRoutePointEditTarget)
9149 .EnableDragHandle(false);
9150 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9151 SELTYPE_DRAGHANDLE);
9152
9153 // Clear any routes being edited, probably orphans
9154 wxArrayPtrVoid *lastEditRouteArray =
9155 g_pRouteMan->GetRouteArrayContaining(
9156 m_lastRoutePointEditTarget);
9157 if (lastEditRouteArray) {
9158 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9159 ir++) {
9160 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9161 if (g_pRouteMan->IsRouteValid(pr)) {
9162 pr->m_bIsBeingEdited = false;
9163 }
9164 }
9165 }
9166 }
9167 }
9168
9169 // Do the refresh
9170
9171 if (g_bopengl) {
9172 InvalidateGL();
9173 Refresh(false);
9174 } else {
9175 if (m_lastRoutePointEditTarget) {
9176 wxRect wp_rect;
9177 RoutePointGui(*m_lastRoutePointEditTarget)
9178 .CalculateDCRect(m_dc_route, this, &wp_rect);
9179 RefreshRect(wp_rect, true);
9180 }
9181
9182 if (m_pRoutePointEditTarget) {
9183 wxRect wp_rect;
9184 RoutePointGui(*m_pRoutePointEditTarget)
9185 .CalculateDCRect(m_dc_route, this, &wp_rect);
9186 RefreshRect(wp_rect, true);
9187 }
9188 }
9189 }
9190 } // bSelectAllowed
9191
9192 // Check to see if there is a route or AIS target under the cursor
9193 // If so, start the rollover timer which creates the popup
9194 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9195 bool b_start_rollover = false;
9196 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9197 SelectItem *pFind = pSelectAIS->FindSelection(
9198 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9199 if (pFind) b_start_rollover = true;
9200 }
9201
9202 if (!b_start_rollover && !b_startedit_route) {
9203 SelectableItemList SelList = pSelect->FindSelectionList(
9204 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9205 wxSelectableItemListNode *node = SelList.GetFirst();
9206 while (node) {
9207 SelectItem *pFindSel = node->GetData();
9208
9209 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9210
9211 if (pr && pr->IsVisible()) {
9212 b_start_rollover = true;
9213 break;
9214 }
9215 node = node->GetNext();
9216 } // while
9217 }
9218
9219 if (!b_start_rollover && !b_startedit_route) {
9220 SelectableItemList SelList = pSelect->FindSelectionList(
9221 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9222 wxSelectableItemListNode *node = SelList.GetFirst();
9223 while (node) {
9224 SelectItem *pFindSel = node->GetData();
9225
9226 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9227
9228 if (tr && tr->IsVisible()) {
9229 b_start_rollover = true;
9230 break;
9231 }
9232 node = node->GetNext();
9233 } // while
9234 }
9235
9236 if (b_start_rollover)
9237 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9238 wxTIMER_ONE_SHOT);
9239 Route *tail = 0;
9240 Route *current = 0;
9241 bool appending = false;
9242 bool inserting = false;
9243 int connect = 0;
9244 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9245 // drag
9246 if (m_pRoutePointEditTarget) {
9247 // Check to see if there is a nearby point which may replace the
9248 // dragged one
9249 RoutePoint *pMousePoint = NULL;
9250
9251 int index_last;
9252 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9253 double nearby_radius_meters =
9254 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9255 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9256 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9257 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9258 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9259 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9260 bool duplicate =
9261 false; // ensure we won't create duplicate point in routes
9262 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9263 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9264 ir++) {
9265 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9266 if (pr && pr->pRoutePointList) {
9267 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9268 wxNOT_FOUND) {
9269 duplicate = true;
9270 break;
9271 }
9272 }
9273 }
9274 }
9275
9276 // Special case:
9277 // Allow "re-use" of a route's waypoints iff it is a simple
9278 // isolated route. This allows, for instance, creation of a closed
9279 // polygon route
9280 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9281
9282 if (!duplicate) {
9283 int dlg_return;
9284 dlg_return =
9285 OCPNMessageBox(this,
9286 _("Replace this RoutePoint by the nearby "
9287 "Waypoint?"),
9288 _("OpenCPN RoutePoint change"),
9289 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9290 if (dlg_return == wxID_YES) {
9291 /*double confirmation if the dragged point has been manually
9292 * created which can be important and could be deleted
9293 * unintentionally*/
9294
9295 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9296 pNearbyPoint);
9297 current =
9298 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9299
9300 if (tail && current && (tail != current)) {
9301 int dlg_return1;
9302 connect = tail->GetIndexOf(pNearbyPoint);
9303 int index_current_route =
9304 current->GetIndexOf(m_pRoutePointEditTarget);
9305 index_last = current->GetIndexOf(current->GetLastPoint());
9306 dlg_return1 = wxID_NO;
9307 if (index_last ==
9308 index_current_route) { // we are dragging the last
9309 // point of the route
9310 if (connect != tail->GetnPoints()) { // anything to do?
9311
9312 wxString dmsg(
9313 _("Last part of route to be appended to dragged "
9314 "route?"));
9315 if (connect == 1)
9316 dmsg =
9317 _("Full route to be appended to dragged route?");
9318
9319 dlg_return1 = OCPNMessageBox(
9320 this, dmsg, _("OpenCPN Route Create"),
9321 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9322 if (dlg_return1 == wxID_YES) {
9323 appending = true;
9324 }
9325 }
9326 } else if (index_current_route ==
9327 1) { // dragging the first point of the route
9328 if (connect != 1) { // anything to do?
9329
9330 wxString dmsg(
9331 _("First part of route to be inserted into dragged "
9332 "route?"));
9333 if (connect == tail->GetnPoints())
9334 dmsg = _(
9335 "Full route to be inserted into dragged route?");
9336
9337 dlg_return1 = OCPNMessageBox(
9338 this, dmsg, _("OpenCPN Route Create"),
9339 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9340 if (dlg_return1 == wxID_YES) {
9341 inserting = true;
9342 }
9343 }
9344 }
9345 }
9346
9347 if (m_pRoutePointEditTarget->IsShared()) {
9348 // dlg_return = wxID_NO;
9349 dlg_return = OCPNMessageBox(
9350 this,
9351 _("Do you really want to delete and replace this "
9352 "WayPoint") +
9353 _T("\n") + _("which has been created manually?"),
9354 ("OpenCPN RoutePoint warning"),
9355 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9356 }
9357 }
9358 if (dlg_return == wxID_YES) {
9359 pMousePoint = pNearbyPoint;
9360 if (pMousePoint->m_bIsolatedMark) {
9361 pMousePoint->SetShared(true);
9362 }
9363 pMousePoint->m_bIsolatedMark =
9364 false; // definitely no longer isolated
9365 pMousePoint->m_bIsInRoute = true;
9366 }
9367 }
9368 }
9369 }
9370 if (!pMousePoint)
9371 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9372
9373 if (m_pEditRouteArray) {
9374 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9375 ir++) {
9376 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9377 if (g_pRouteMan->IsRouteValid(pr)) {
9378 if (pMousePoint) { // remove the dragged point and insert the
9379 // nearby
9380 int nRP =
9381 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9382
9383 pSelect->DeleteAllSelectableRoutePoints(pr);
9384 pSelect->DeleteAllSelectableRouteSegments(pr);
9385
9386 pr->pRoutePointList->Insert(nRP, pMousePoint);
9387 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9388
9389 pSelect->AddAllSelectableRouteSegments(pr);
9390 pSelect->AddAllSelectableRoutePoints(pr);
9391 }
9392 pr->FinalizeForRendering();
9393 pr->UpdateSegmentDistances();
9394 if (m_bRoutePoinDragging) pConfig->UpdateRoute(pr);
9395 }
9396 }
9397 }
9398
9399 // Update the RouteProperties Dialog, if currently shown
9400 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9401 if (m_pEditRouteArray) {
9402 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9403 ir++) {
9404 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9405 if (g_pRouteMan->IsRouteValid(pr)) {
9406 if (pRoutePropDialog->GetRoute() == pr) {
9407 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9408 }
9409 /* cannot edit track points anyway
9410 else if ( ( NULL !=
9411 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9412 pTrackPropDialog->m_pTrack == pr ) {
9413 pTrackPropDialog->SetTrackAndUpdate(
9414 pr );
9415 }
9416 */
9417 }
9418 }
9419 }
9420 }
9421 if (pMousePoint) { // clear all about the dragged point
9422 pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9423 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9424 // Hide mark properties dialog if open on the replaced point
9425 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9426 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9427 g_pMarkInfoDialog->Hide();
9428
9429 delete m_pRoutePointEditTarget;
9430 m_lastRoutePointEditTarget = NULL;
9431 m_pRoutePointEditTarget = NULL;
9432 undo->AfterUndoableAction(pMousePoint);
9433 undo->InvalidateUndo();
9434 }
9435 }
9436 }
9437
9438 else if (m_bMarkEditing) { // End of way point drag
9439 if (m_pRoutePointEditTarget)
9440 if (m_bRoutePoinDragging)
9441 pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9442 }
9443
9444 if (m_pRoutePointEditTarget)
9445 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9446
9447 if (!m_pRoutePointEditTarget) {
9448 delete m_pEditRouteArray;
9449 m_pEditRouteArray = NULL;
9450 m_bRouteEditing = false;
9451 }
9452 m_bRoutePoinDragging = false;
9453
9454 if (appending) { // Appending to the route of which the last point is
9455 // dragged onto another route
9456
9457 // copy tail from connect until length to end of current after dragging
9458
9459 int length = tail->GetnPoints();
9460 for (int i = connect + 1; i <= length; i++) {
9461 current->AddPointAndSegment(tail->GetPoint(i), false);
9462 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9463 m_routeState++;
9464 gFrame->RefreshAllCanvas();
9465 ret = true;
9466 }
9467 current->FinalizeForRendering();
9468 current->m_bIsBeingEdited = false;
9469 FinishRoute();
9470 g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9471 }
9472 if (inserting) {
9473 pSelect->DeleteAllSelectableRoutePoints(current);
9474 pSelect->DeleteAllSelectableRouteSegments(current);
9475 for (int i = 1; i < connect; i++) { // numbering in the tail route
9476 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9477 }
9478 pSelect->AddAllSelectableRouteSegments(current);
9479 pSelect->AddAllSelectableRoutePoints(current);
9480 current->FinalizeForRendering();
9481 current->m_bIsBeingEdited = false;
9482 g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9483 }
9484
9485 // Update the RouteProperties Dialog, if currently shown
9486 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9487 if (m_pEditRouteArray) {
9488 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9489 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9490 if (g_pRouteMan->IsRouteValid(pr)) {
9491 if (pRoutePropDialog->GetRoute() == pr) {
9492 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9493 }
9494 }
9495 }
9496 }
9497 }
9498
9499 } // g_btouch
9500
9501 else { // !g_btouch
9502 if (m_bRouteEditing) { // End of RoutePoint drag
9503 Route *tail = 0;
9504 Route *current = 0;
9505 bool appending = false;
9506 bool inserting = false;
9507 int connect = 0;
9508 int index_last;
9509 if (m_pRoutePointEditTarget) {
9510 m_pRoutePointEditTarget->m_bBlink = false;
9511 // Check to see if there is a nearby point which may replace the
9512 // dragged one
9513 RoutePoint *pMousePoint = NULL;
9514 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9515 double nearby_radius_meters =
9516 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9517 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9518 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9519 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9520 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9521 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9522 bool duplicate = false; // don't create duplicate point in routes
9523 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9524 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9525 ir++) {
9526 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9527 if (pr && pr->pRoutePointList) {
9528 if (pr->pRoutePointList->IndexOf(pNearbyPoint) !=
9529 wxNOT_FOUND) {
9530 duplicate = true;
9531 break;
9532 }
9533 }
9534 }
9535 }
9536
9537 // Special case:
9538 // Allow "re-use" of a route's waypoints iff it is a simple
9539 // isolated route. This allows, for instance, creation of a closed
9540 // polygon route
9541 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9542
9543 if (!duplicate) {
9544 int dlg_return;
9545 dlg_return =
9546 OCPNMessageBox(this,
9547 _("Replace this RoutePoint by the nearby "
9548 "Waypoint?"),
9549 _("OpenCPN RoutePoint change"),
9550 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9551 if (dlg_return == wxID_YES) {
9552 /*double confirmation if the dragged point has been manually
9553 * created which can be important and could be deleted
9554 * unintentionally*/
9555 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9556 pNearbyPoint);
9557 current =
9558 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9559
9560 if (tail && current && (tail != current)) {
9561 int dlg_return1;
9562 connect = tail->GetIndexOf(pNearbyPoint);
9563 int index_current_route =
9564 current->GetIndexOf(m_pRoutePointEditTarget);
9565 index_last = current->GetIndexOf(current->GetLastPoint());
9566 dlg_return1 = wxID_NO;
9567 if (index_last ==
9568 index_current_route) { // we are dragging the last
9569 // point of the route
9570 if (connect != tail->GetnPoints()) { // anything to do?
9571
9572 wxString dmsg(
9573 _("Last part of route to be appended to dragged "
9574 "route?"));
9575 if (connect == 1)
9576 dmsg =
9577 _("Full route to be appended to dragged route?");
9578
9579 dlg_return1 = OCPNMessageBox(
9580 this, dmsg, _("OpenCPN Route Create"),
9581 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9582 if (dlg_return1 == wxID_YES) {
9583 appending = true;
9584 }
9585 }
9586 } else if (index_current_route ==
9587 1) { // dragging the first point of the route
9588 if (connect != 1) { // anything to do?
9589
9590 wxString dmsg(
9591 _("First part of route to be inserted into dragged "
9592 "route?"));
9593 if (connect == tail->GetnPoints())
9594 dmsg = _(
9595 "Full route to be inserted into dragged route?");
9596
9597 dlg_return1 = OCPNMessageBox(
9598 this, dmsg, _("OpenCPN Route Create"),
9599 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9600 if (dlg_return1 == wxID_YES) {
9601 inserting = true;
9602 }
9603 }
9604 }
9605 }
9606
9607 if (m_pRoutePointEditTarget->IsShared()) {
9608 dlg_return = wxID_NO;
9609 dlg_return = OCPNMessageBox(
9610 this,
9611 _("Do you really want to delete and replace this "
9612 "WayPoint") +
9613 _T("\n") + _("which has been created manually?"),
9614 ("OpenCPN RoutePoint warning"),
9615 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9616 }
9617 }
9618 if (dlg_return == wxID_YES) {
9619 pMousePoint = pNearbyPoint;
9620 if (pMousePoint->m_bIsolatedMark) {
9621 pMousePoint->SetShared(true);
9622 }
9623 pMousePoint->m_bIsolatedMark =
9624 false; // definitely no longer isolated
9625 pMousePoint->m_bIsInRoute = true;
9626 }
9627 }
9628 }
9629 }
9630 if (!pMousePoint)
9631 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9632
9633 if (m_pEditRouteArray) {
9634 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9635 ir++) {
9636 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9637 if (g_pRouteMan->IsRouteValid(pr)) {
9638 if (pMousePoint) { // replace dragged point by nearby one
9639 int nRP =
9640 pr->pRoutePointList->IndexOf(m_pRoutePointEditTarget);
9641
9642 pSelect->DeleteAllSelectableRoutePoints(pr);
9643 pSelect->DeleteAllSelectableRouteSegments(pr);
9644
9645 pr->pRoutePointList->Insert(nRP, pMousePoint);
9646 pr->pRoutePointList->DeleteObject(m_pRoutePointEditTarget);
9647
9648 pSelect->AddAllSelectableRouteSegments(pr);
9649 pSelect->AddAllSelectableRoutePoints(pr);
9650 }
9651 pr->FinalizeForRendering();
9652 pr->UpdateSegmentDistances();
9653 pr->m_bIsBeingEdited = false;
9654
9655 if (m_bRoutePoinDragging) pConfig->UpdateRoute(pr);
9656
9657 pr->SetHiLite(0);
9658 }
9659 }
9660 Refresh(false);
9661 }
9662
9663 if (appending) {
9664 // copy tail from connect until length to end of current after
9665 // dragging
9666
9667 int length = tail->GetnPoints();
9668 for (int i = connect + 1; i <= length; i++) {
9669 current->AddPointAndSegment(tail->GetPoint(i), false);
9670 if (current)
9671 current->m_lastMousePointIndex = current->GetnPoints();
9672 m_routeState++;
9673 gFrame->RefreshAllCanvas();
9674 ret = true;
9675 }
9676 current->FinalizeForRendering();
9677 current->m_bIsBeingEdited = false;
9678 FinishRoute();
9679 g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9680 }
9681 if (inserting) {
9682 pSelect->DeleteAllSelectableRoutePoints(current);
9683 pSelect->DeleteAllSelectableRouteSegments(current);
9684 for (int i = 1; i < connect; i++) { // numbering in the tail route
9685 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9686 }
9687 pSelect->AddAllSelectableRouteSegments(current);
9688 pSelect->AddAllSelectableRoutePoints(current);
9689 current->FinalizeForRendering();
9690 current->m_bIsBeingEdited = false;
9691 g_pRouteMan->DeleteRoute(tail, NavObjectChanges::getInstance());
9692 }
9693
9694 // Update the RouteProperties Dialog, if currently shown
9695 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9696 if (m_pEditRouteArray) {
9697 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9698 ir++) {
9699 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9700 if (g_pRouteMan->IsRouteValid(pr)) {
9701 if (pRoutePropDialog->GetRoute() == pr) {
9702 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9703 }
9704 }
9705 }
9706 }
9707 }
9708
9709 if (pMousePoint) {
9710 pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9711 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9712 // Hide mark properties dialog if open on the replaced point
9713 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9714 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9715 g_pMarkInfoDialog->Hide();
9716
9717 delete m_pRoutePointEditTarget;
9718 m_lastRoutePointEditTarget = NULL;
9719 undo->AfterUndoableAction(pMousePoint);
9720 undo->InvalidateUndo();
9721 } else {
9722 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9723 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9724
9725 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9726 }
9727
9728 delete m_pEditRouteArray;
9729 m_pEditRouteArray = NULL;
9730 }
9731
9732 InvalidateGL();
9733 m_bRouteEditing = false;
9734 m_pRoutePointEditTarget = NULL;
9735
9736 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9737 ret = true;
9738 }
9739
9740 else if (m_bMarkEditing) { // end of Waypoint drag
9741 if (m_pRoutePointEditTarget) {
9742 if (m_bRoutePoinDragging)
9743 pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9744 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9745 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9746 if (!g_bopengl) {
9747 wxRect wp_rect;
9748 RoutePointGui(*m_pRoutePointEditTarget)
9749 .CalculateDCRect(m_dc_route, this, &wp_rect);
9750 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9751 RefreshRect(wp_rect, true);
9752 }
9753 }
9754 m_pRoutePointEditTarget = NULL;
9755 m_bMarkEditing = false;
9756 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9757 ret = true;
9758 }
9759
9760 else if (leftIsDown) { // left click for chart center
9761 leftIsDown = false;
9762 ret = false;
9763
9764 if (!g_btouch) {
9765 if (!m_bChartDragging && !m_bMeasure_Active) {
9766 } else {
9767 m_bChartDragging = false;
9768 }
9769 }
9770 }
9771 m_bRoutePoinDragging = false;
9772 } // !btouch
9773
9774 if (ret) return true;
9775 } // left up
9776
9777 if (event.RightDown()) {
9778 SetFocus(); // This is to let a plugin know which canvas is right-clicked
9779 last_drag.x = mx;
9780 last_drag.y = my;
9781
9782 if (g_btouch) {
9783 // if( m_pRoutePointEditTarget )
9784 // return false;
9785 }
9786
9787 ret = true;
9788 m_FinishRouteOnKillFocus = false;
9789 CallPopupMenu(mx, my);
9790 m_FinishRouteOnKillFocus = true;
9791 } // Right down
9792
9793 return ret;
9794}
9795
9796bool panleftIsDown;
9797
9798bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
9799 int x, y;
9800 event.GetPosition(&x, &y);
9801
9802 x *= m_displayScale;
9803 y *= m_displayScale;
9804
9805 // Check for wheel rotation
9806 // ideally, should be just longer than the time between
9807 // processing accumulated mouse events from the event queue
9808 // as would happen during screen redraws.
9809 int wheel_dir = event.GetWheelRotation();
9810
9811 if (wheel_dir) {
9812 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
9813 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
9814
9815 double factor = g_mouse_zoom_sensitivity;
9816 if (wheel_dir < 0) factor = 1 / factor;
9817
9818 if (g_bsmoothpanzoom) {
9819 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
9820 if (wheel_dir == m_last_wheel_dir) {
9821 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
9822 // m_zoom_target /= factor;
9823 } else
9824 StopMovement();
9825 } else {
9826 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
9827 m_wheelstopwatch.Start(0);
9828 // m_zoom_target = VPoint.chart_scale / factor;
9829 }
9830 }
9831
9832 m_last_wheel_dir = wheel_dir;
9833
9834 ZoomCanvas(factor, true, false);
9835 }
9836
9837 if (event.LeftDown()) {
9838 // Skip the first left click if it will cause a canvas focus shift
9839 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
9840 // printf("focus shift\n");
9841 return false;
9842 }
9843
9844 last_drag.x = x, last_drag.y = y;
9845 panleftIsDown = true;
9846 }
9847
9848 if (event.LeftUp()) {
9849 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
9850 // seen here.
9851 panleftIsDown = false;
9852
9853 if (!g_btouch) {
9854 if (!m_bChartDragging && !m_bMeasure_Active) {
9855 switch (cursor_region) {
9856 case MID_RIGHT: {
9857 PanCanvas(100, 0);
9858 break;
9859 }
9860
9861 case MID_LEFT: {
9862 PanCanvas(-100, 0);
9863 break;
9864 }
9865
9866 case MID_TOP: {
9867 PanCanvas(0, 100);
9868 break;
9869 }
9870
9871 case MID_BOT: {
9872 PanCanvas(0, -100);
9873 break;
9874 }
9875
9876 case CENTER: {
9877 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
9878 break;
9879 }
9880 }
9881 } else {
9882 m_bChartDragging = false;
9883 }
9884 }
9885 }
9886 }
9887
9888 if (event.Dragging() && event.LeftIsDown()) {
9889 /*
9890 * fixed dragging.
9891 * On my Surface Pro 3 running Arch Linux there is no mouse down event
9892 * before the drag event. Hence, as there is no mouse down event, last_drag
9893 * is not reset before the drag. And that results in one single drag
9894 * session, meaning you cannot drag the map a few miles north, lift your
9895 * finger, and the go even further north. Instead, the map resets itself
9896 * always to the very first drag start (since there is not reset of
9897 * last_drag).
9898 *
9899 * Besides, should not left down and dragging be enough of a situation to
9900 * start a drag procedure?
9901 *
9902 * Anyways, guarded it to be active in touch situations only.
9903 */
9904
9905 if (g_btouch) {
9906 struct timespec now;
9907 clock_gettime(CLOCK_MONOTONIC, &now);
9908 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
9909
9910 if (false == m_bChartDragging) {
9911 // Reset drag calculation members
9912 last_drag.x = x, last_drag.y = y;
9913 m_bChartDragging = true;
9914 m_chart_drag_total_time = 0;
9915 m_chart_drag_total_x = 0;
9916 m_chart_drag_total_y = 0;
9917 m_inertia_last_drag_x = x;
9918 m_inertia_last_drag_y = y;
9919 m_drag_vec_x.clear();
9920 m_drag_vec_y.clear();
9921 m_drag_vec_t.clear();
9922 m_last_drag_time = tnow;
9923 }
9924
9925 // Calculate and store drag dynamics.
9926 uint64_t delta_t = tnow - m_last_drag_time;
9927 double delta_tf = delta_t / 1e9;
9928
9929 m_chart_drag_total_time += delta_tf;
9930 m_chart_drag_total_x += m_inertia_last_drag_x - x;
9931 m_chart_drag_total_y += m_inertia_last_drag_y - y;
9932
9933 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
9934 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
9935 m_drag_vec_t.push_back(delta_tf);
9936
9937 m_inertia_last_drag_x = x;
9938 m_inertia_last_drag_y = y;
9939 m_last_drag_time = tnow;
9940
9941 if ((last_drag.x != x) || (last_drag.y != y)) {
9942 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
9943 // dragging on route create.
9944 // github #2994
9945 m_bChartDragging = true;
9946 StartTimedMovement();
9947 m_pan_drag.x += last_drag.x - x;
9948 m_pan_drag.y += last_drag.y - y;
9949 last_drag.x = x, last_drag.y = y;
9950 }
9951 }
9952 } else {
9953 if ((last_drag.x != x) || (last_drag.y != y)) {
9954 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
9955 // dragging on route create.
9956 // github #2994
9957 m_bChartDragging = true;
9958 StartTimedMovement();
9959 m_pan_drag.x += last_drag.x - x;
9960 m_pan_drag.y += last_drag.y - y;
9961 last_drag.x = x, last_drag.y = y;
9962 }
9963 }
9964 }
9965
9966 // Handle some special cases
9967 if (g_btouch) {
9968 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
9969 // deactivate next LeftUp to ovoid creating an unexpected point
9970 m_DoubleClickTimer->Start();
9971 singleClickEventIsValid = false;
9972 }
9973 }
9974 }
9975
9976 return true;
9977}
9978
9979void ChartCanvas::MouseEvent(wxMouseEvent &event) {
9980 if (MouseEventOverlayWindows(event)) return;
9981
9982 if (MouseEventSetup(event)) return; // handled, no further action required
9983
9984 if (!MouseEventProcessObjects(event)) MouseEventProcessCanvas(event);
9985}
9986
9987void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
9988 // Switch to the appropriate cursor on mouse movement
9989
9990 wxCursor *ptarget_cursor = pCursorArrow;
9991 if (!pPlugIn_Cursor) {
9992 ptarget_cursor = pCursorArrow;
9993 if ((!m_routeState) &&
9994 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
9995 if (cursor_region == MID_RIGHT) {
9996 ptarget_cursor = pCursorRight;
9997 } else if (cursor_region == MID_LEFT) {
9998 ptarget_cursor = pCursorLeft;
9999 } else if (cursor_region == MID_TOP) {
10000 ptarget_cursor = pCursorDown;
10001 } else if (cursor_region == MID_BOT) {
10002 ptarget_cursor = pCursorUp;
10003 } else {
10004 ptarget_cursor = pCursorArrow;
10005 }
10006 } else if (m_bMeasure_Active ||
10007 m_routeState) // If Measure tool use Pencil Cursor
10008 ptarget_cursor = pCursorPencil;
10009 } else {
10010 ptarget_cursor = pPlugIn_Cursor;
10011 }
10012
10013 SetCursor(*ptarget_cursor);
10014}
10015
10016void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10017 SetCursor(*pCursorArrow);
10018}
10019
10020void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10021 ChartPlugInWrapper *target_plugin_chart = NULL;
10022 s57chart *Chs57 = NULL;
10023 wxFileName file;
10024 wxArrayString files;
10025
10026 ChartBase *target_chart = GetChartAtCursor();
10027 if (target_chart) {
10028 file.Assign(target_chart->GetFullPath());
10029 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10030 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10031 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10032 else
10033 Chs57 = dynamic_cast<s57chart *>(target_chart);
10034 } else { // target_chart = null, might be mbtiles
10035 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10036 unsigned int im = stackIndexArray.size();
10037 int scale = 2147483647; // max 32b integer
10038 if (VPoint.b_quilt && im > 0) {
10039 for (unsigned int is = 0; is < im; is++) {
10040 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10041 CHART_TYPE_MBTILES) {
10042 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10043 double lat, lon;
10044 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10045 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10046 .GetBBox()
10047 .Contains(lat, lon)) {
10048 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10049 scale) {
10050 scale =
10051 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10052 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10053 }
10054 }
10055 }
10056 }
10057 }
10058 }
10059
10060 std::vector<Ais8_001_22 *> area_notices;
10061
10062 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10063 float vp_scale = GetVPScale();
10064
10065 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10066 auto target_data = target.second;
10067 if (!target_data->area_notices.empty()) {
10068 for (auto &ani : target_data->area_notices) {
10069 Ais8_001_22 &area_notice = ani.second;
10070
10071 BoundingBox bbox;
10072
10073 for (Ais8_001_22_SubAreaList::iterator sa =
10074 area_notice.sub_areas.begin();
10075 sa != area_notice.sub_areas.end(); ++sa) {
10076 switch (sa->shape) {
10077 case AIS8_001_22_SHAPE_CIRCLE: {
10078 wxPoint target_point;
10079 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10080 bbox.Expand(target_point);
10081 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10082 break;
10083 }
10084 case AIS8_001_22_SHAPE_RECT: {
10085 wxPoint target_point;
10086 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10087 bbox.Expand(target_point);
10088 if (sa->e_dim_m > sa->n_dim_m)
10089 bbox.EnLarge(sa->e_dim_m * vp_scale);
10090 else
10091 bbox.EnLarge(sa->n_dim_m * vp_scale);
10092 break;
10093 }
10094 case AIS8_001_22_SHAPE_POLYGON:
10095 case AIS8_001_22_SHAPE_POLYLINE: {
10096 for (int i = 0; i < 4; ++i) {
10097 double lat = sa->latitude;
10098 double lon = sa->longitude;
10099 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10100 &lat, &lon);
10101 wxPoint target_point;
10102 GetCanvasPointPix(lat, lon, &target_point);
10103 bbox.Expand(target_point);
10104 }
10105 break;
10106 }
10107 case AIS8_001_22_SHAPE_SECTOR: {
10108 double lat1 = sa->latitude;
10109 double lon1 = sa->longitude;
10110 double lat, lon;
10111 wxPoint target_point;
10112 GetCanvasPointPix(lat1, lon1, &target_point);
10113 bbox.Expand(target_point);
10114 for (int i = 0; i < 18; ++i) {
10115 ll_gc_ll(
10116 lat1, lon1,
10117 sa->left_bound_deg +
10118 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10119 sa->radius_m / 1852.0, &lat, &lon);
10120 GetCanvasPointPix(lat, lon, &target_point);
10121 bbox.Expand(target_point);
10122 }
10123 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10124 &lat, &lon);
10125 GetCanvasPointPix(lat, lon, &target_point);
10126 bbox.Expand(target_point);
10127 break;
10128 }
10129 }
10130 }
10131
10132 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10133 area_notices.push_back(&area_notice);
10134 }
10135 }
10136 }
10137 }
10138 }
10139
10140 if (target_chart || !area_notices.empty() || file.HasName()) {
10141 // Go get the array of all objects at the cursor lat/lon
10142 int sel_rad_pix = 5;
10143 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10144
10145 // Make sure we always get the lights from an object, even if we are
10146 // currently not displaying lights on the chart.
10147
10148 SetCursor(wxCURSOR_WAIT);
10149 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10150 if (!lightsVis) SetShowENCLights(true);
10151 ;
10152
10153 ListOfObjRazRules *rule_list = NULL;
10154 ListOfPI_S57Obj *pi_rule_list = NULL;
10155 if (Chs57)
10156 rule_list =
10157 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10158 else if (target_plugin_chart)
10159 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10160 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10161
10162 ListOfObjRazRules *overlay_rule_list = NULL;
10163 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10164 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10165
10166 if (CHs57_Overlay) {
10167 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10168 zlat, zlon, SelectRadius, &GetVP());
10169 }
10170
10171 if (!lightsVis) SetShowENCLights(false);
10172
10173 wxString objText;
10174 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10175 wxString face = dFont->GetFaceName();
10176
10177 if (NULL == g_pObjectQueryDialog) {
10178 g_pObjectQueryDialog =
10179 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10180 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10181 }
10182
10183 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10184 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10185
10186#ifdef __WXOSX__
10187 // Auto Adjustment for dark mode
10188 fg = g_pObjectQueryDialog->GetForegroundColour();
10189#endif
10190
10191 objText.Printf(
10192 _T("<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>"),
10193 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10194
10195#ifdef __WXOSX__
10196 int points = dFont->GetPointSize();
10197#else
10198 int points = dFont->GetPointSize() + 1;
10199#endif
10200
10201 int sizes[7];
10202 for (int i = -2; i < 5; i++) {
10203 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10204 }
10205 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10206
10207 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += _T("<i>");
10208
10209 if (overlay_rule_list && CHs57_Overlay) {
10210 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10211 objText << _T("<hr noshade>");
10212 }
10213
10214 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10215 an != area_notices.end(); ++an) {
10216 objText << _T( "<b>AIS Area Notice:</b> " );
10217 objText << ais8_001_22_notice_names[(*an)->notice_type];
10218 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10219 (*an)->sub_areas.begin();
10220 sa != (*an)->sub_areas.end(); ++sa)
10221 if (!sa->text.empty()) objText << sa->text;
10222 objText << _T( "<br>expires: " ) << (*an)->expiry_time.Format();
10223 objText << _T( "<hr noshade>" );
10224 }
10225
10226 if (Chs57)
10227 objText << Chs57->CreateObjDescriptions(rule_list);
10228 else if (target_plugin_chart)
10229 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10230 pi_rule_list);
10231
10232 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << _T("</i>");
10233
10234 // Add the additional info files
10235 wxString AddFiles, filenameOK;
10236 int filecount = 0;
10237 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10238 // plugin
10239
10240 AddFiles = wxString::Format(
10241 _T("<hr noshade><br><b>Additional info files attached to: </b> ")
10242 _T("<font ")
10243 _T("size=-2>%s</font><br><table border=0 cellspacing=0 ")
10244 _T("cellpadding=3>"),
10245 file.GetFullName());
10246 file.Normalize();
10247 file.Assign(file.GetPath(), wxT(""));
10248 wxDir dir(file.GetFullPath());
10249 wxString filename;
10250 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10251 while (cont) {
10252 file.Assign(dir.GetNameWithSep().append(filename));
10253 wxString FormatString =
10254 _T("<td valign=top><font size=-2><a ")
10255 _T("href=\"%s\">%s</a></font></td>");
10256 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10257 filenameOK = file.GetFullPath(); // remember last valid name
10258 // we are making a 3 columns table. New row only every third file
10259 if (3 * ((int)filecount / 3) == filecount)
10260 FormatString.Prepend(_T("<tr>")); // new row
10261 else
10262 FormatString.Prepend(
10263 _T("<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>")); // an empty
10264 // spacer column
10265
10266 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10267 file.GetFullName());
10268 filecount++;
10269 }
10270 cont = dir.GetNext(&filename);
10271 }
10272 objText << AddFiles << _T("</table>");
10273 }
10274 objText << _T("</font>");
10275 objText << _T("</body></html>");
10276
10277 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10278 g_pObjectQueryDialog->SetHTMLPage(objText);
10279 g_pObjectQueryDialog->Show();
10280 }
10281 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10282 // generate an event to avoid double code
10283 wxHtmlLinkInfo hli(filenameOK);
10284 wxHtmlLinkEvent hle(1, hli);
10285 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10286 }
10287
10288 if (rule_list) rule_list->Clear();
10289 delete rule_list;
10290
10291 if (overlay_rule_list) overlay_rule_list->Clear();
10292 delete overlay_rule_list;
10293
10294 if (pi_rule_list) pi_rule_list->Clear();
10295 delete pi_rule_list;
10296
10297 SetCursor(wxCURSOR_ARROW);
10298 }
10299}
10300
10301void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10302 bool bNew = false;
10303 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10304 // Dialog
10305 g_pMarkInfoDialog = new MarkInfoDlg(this);
10306 bNew = true;
10307 }
10308
10309 if (1 /*g_bresponsive*/) {
10310 wxSize canvas_size = GetSize();
10311
10312 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10313 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10314
10315 g_pMarkInfoDialog->Layout();
10316
10317 wxPoint canvas_pos = GetPosition();
10318 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10319
10320 bool newFit = false;
10321 if (canvas_size.x < fitted_size.x) {
10322 fitted_size.x = canvas_size.x - 40;
10323 if (canvas_size.y < fitted_size.y)
10324 fitted_size.y -= 40; // scrollbar added
10325 }
10326 if (canvas_size.y < fitted_size.y) {
10327 fitted_size.y = canvas_size.y - 40;
10328 if (canvas_size.x < fitted_size.x)
10329 fitted_size.x -= 40; // scrollbar added
10330 }
10331
10332 if (newFit) {
10333 g_pMarkInfoDialog->SetSize(fitted_size);
10334 g_pMarkInfoDialog->Centre();
10335 }
10336 }
10337
10338 markPoint->m_bRPIsBeingEdited = false;
10339
10340 wxString title_base = _("Mark Properties");
10341 if (markPoint->m_bIsInRoute) {
10342 title_base = _("Waypoint Properties");
10343 }
10344 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10345 g_pMarkInfoDialog->UpdateProperties();
10346 if (markPoint->m_bIsInLayer) {
10347 wxString caption(wxString::Format(_T("%s, %s: %s"), title_base, _("Layer"),
10348 GetLayerName(markPoint->m_LayerID)));
10349 g_pMarkInfoDialog->SetDialogTitle(caption);
10350 } else
10351 g_pMarkInfoDialog->SetDialogTitle(title_base);
10352
10353 g_pMarkInfoDialog->Show();
10354 g_pMarkInfoDialog->Raise();
10355 g_pMarkInfoDialog->InitialFocus();
10356 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10357}
10358
10359void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10360 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10361 pRoutePropDialog->SetRouteAndUpdate(selected);
10362 // pNew->UpdateProperties();
10363 pRoutePropDialog->Show();
10364 pRoutePropDialog->Raise();
10365 return;
10366 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10367 this); // There is one global instance of the RouteProp Dialog
10368
10369 if (g_bresponsive) {
10370 wxSize canvas_size = GetSize();
10371 wxPoint canvas_pos = GetPosition();
10372 wxSize fitted_size = pRoutePropDialog->GetSize();
10373 ;
10374
10375 if (canvas_size.x < fitted_size.x) {
10376 fitted_size.x = canvas_size.x;
10377 if (canvas_size.y < fitted_size.y)
10378 fitted_size.y -= 20; // scrollbar added
10379 }
10380 if (canvas_size.y < fitted_size.y) {
10381 fitted_size.y = canvas_size.y;
10382 if (canvas_size.x < fitted_size.x)
10383 fitted_size.x -= 20; // scrollbar added
10384 }
10385
10386 pRoutePropDialog->SetSize(fitted_size);
10387 pRoutePropDialog->Centre();
10388
10389 // int xp = (canvas_size.x - fitted_size.x)/2;
10390 // int yp = (canvas_size.y - fitted_size.y)/2;
10391
10392 wxPoint xxp = ClientToScreen(canvas_pos);
10393 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10394 }
10395
10396 pRoutePropDialog->SetRouteAndUpdate(selected);
10397
10398 pRoutePropDialog->Show();
10399
10400 Refresh(false);
10401}
10402
10403void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10404 pTrackPropDialog = TrackPropDlg::getInstance(
10405 this); // There is one global instance of the RouteProp Dialog
10406
10407 pTrackPropDialog->SetTrackAndUpdate(selected);
10408 pTrackPropDialog->UpdateProperties();
10409
10410 pTrackPropDialog->Show();
10411
10412 Refresh(false);
10413}
10414
10415void pupHandler_PasteWaypoint() {
10416 Kml kml;
10417
10418 int pasteBuffer = kml.ParsePasteBuffer();
10419 RoutePoint *pasted = kml.GetParsedRoutePoint();
10420 if (!pasted) return;
10421
10422 double nearby_radius_meters =
10423 g_Platform->GetSelectRadiusPix() /
10424 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10425
10426 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10427 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10428
10429 int answer = wxID_NO;
10430 if (nearPoint && !nearPoint->m_bIsInLayer) {
10431 wxString msg;
10432 msg << _(
10433 "There is an existing waypoint at the same location as the one you are "
10434 "pasting. Would you like to merge the pasted data with it?\n\n");
10435 msg << _("Answering 'No' will create a new waypoint at the same location.");
10436 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10437 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10438 }
10439
10440 if (answer == wxID_YES) {
10441 nearPoint->SetName(pasted->GetName());
10442 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10443 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10444 pRouteManagerDialog->UpdateWptListCtrl();
10445 }
10446
10447 if (answer == wxID_NO) {
10448 RoutePoint *newPoint = new RoutePoint(pasted);
10449 newPoint->m_bIsolatedMark = true;
10450 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10451 newPoint);
10452 pConfig->AddNewWayPoint(newPoint, -1);
10453 pWayPointMan->AddRoutePoint(newPoint);
10454 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10455 pRouteManagerDialog->UpdateWptListCtrl();
10456 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10457 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10458 }
10459
10460 gFrame->InvalidateAllGL();
10461 gFrame->RefreshAllCanvas(false);
10462}
10463
10464void pupHandler_PasteRoute() {
10465 Kml kml;
10466
10467 int pasteBuffer = kml.ParsePasteBuffer();
10468 Route *pasted = kml.GetParsedRoute();
10469 if (!pasted) return;
10470
10471 double nearby_radius_meters =
10472 g_Platform->GetSelectRadiusPix() /
10473 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10474
10475 RoutePoint *curPoint;
10476 RoutePoint *nearPoint;
10477 RoutePoint *prevPoint = NULL;
10478
10479 bool mergepoints = false;
10480 bool createNewRoute = true;
10481 int existingWaypointCounter = 0;
10482
10483 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10484 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10485 nearPoint = pWayPointMan->GetNearbyWaypoint(
10486 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10487 if (nearPoint) {
10488 mergepoints = true;
10489 existingWaypointCounter++;
10490 // Small hack here to avoid both extending RoutePoint and repeating all
10491 // the GetNearbyWaypoint calculations. Use existin data field in
10492 // RoutePoint as temporary storage.
10493 curPoint->m_bPtIsSelected = true;
10494 }
10495 }
10496
10497 int answer = wxID_NO;
10498 if (mergepoints) {
10499 wxString msg;
10500 msg << _(
10501 "There are existing waypoints at the same location as some of the ones "
10502 "you are pasting. Would you like to just merge the pasted data into "
10503 "them?\n\n");
10504 msg << _("Answering 'No' will create all new waypoints for this route.");
10505 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10506 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10507
10508 if (answer == wxID_CANCEL) {
10509 return;
10510 }
10511 }
10512
10513 // If all waypoints exist since before, and a route with the same name, we
10514 // don't create a new route.
10515 if (mergepoints && answer == wxID_YES &&
10516 existingWaypointCounter == pasted->GetnPoints()) {
10517 wxRouteListNode *route_node = pRouteList->GetFirst();
10518 while (route_node) {
10519 Route *proute = route_node->GetData();
10520
10521 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10522 createNewRoute = false;
10523 break;
10524 }
10525 route_node = route_node->GetNext();
10526 }
10527 }
10528
10529 Route *newRoute = 0;
10530 RoutePoint *newPoint = 0;
10531
10532 if (createNewRoute) {
10533 newRoute = new Route();
10534 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10535 }
10536
10537 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10538 curPoint = pasted->GetPoint(i);
10539 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10540 curPoint->m_bPtIsSelected = false;
10541 newPoint = pWayPointMan->GetNearbyWaypoint(
10542 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10543 newPoint->SetName(curPoint->GetName());
10544 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10545
10546 if (createNewRoute) newRoute->AddPoint(newPoint);
10547 } else {
10548 curPoint->m_bPtIsSelected = false;
10549
10550 newPoint = new RoutePoint(curPoint);
10551 newPoint->m_bIsolatedMark = false;
10552 newPoint->SetIconName(_T("circle"));
10553 newPoint->m_bIsVisible = true;
10554 newPoint->m_bShowName = false;
10555 newPoint->SetShared(false);
10556
10557 newRoute->AddPoint(newPoint);
10558 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10559 newPoint);
10560 pConfig->AddNewWayPoint(newPoint, -1);
10561 pWayPointMan->AddRoutePoint(newPoint);
10562 }
10563 if (i > 1 && createNewRoute)
10564 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10565 curPoint->m_lat, curPoint->m_lon,
10566 prevPoint, newPoint, newRoute);
10567 prevPoint = newPoint;
10568 }
10569
10570 if (createNewRoute) {
10571 pRouteList->Append(newRoute);
10572 pConfig->AddNewRoute(newRoute); // use auto next num
10573
10574 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10575 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10576 }
10577
10578 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10579 pRouteManagerDialog->UpdateRouteListCtrl();
10580 pRouteManagerDialog->UpdateWptListCtrl();
10581 }
10582 gFrame->InvalidateAllGL();
10583 gFrame->RefreshAllCanvas(false);
10584 }
10585 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10586 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10587}
10588
10589void pupHandler_PasteTrack() {
10590 Kml kml;
10591
10592 int pasteBuffer = kml.ParsePasteBuffer();
10593 Track *pasted = kml.GetParsedTrack();
10594 if (!pasted) return;
10595
10596 TrackPoint *curPoint;
10597
10598 Track *newTrack = new Track();
10599 TrackPoint *newPoint;
10600 TrackPoint *prevPoint = NULL;
10601
10602 newTrack->SetName(pasted->GetName());
10603
10604 for (int i = 0; i < pasted->GetnPoints(); i++) {
10605 curPoint = pasted->GetPoint(i);
10606
10607 newPoint = new TrackPoint(curPoint);
10608
10609 wxDateTime now = wxDateTime::Now();
10610 newPoint->SetCreateTime(curPoint->GetCreateTime());
10611
10612 newTrack->AddPoint(newPoint);
10613
10614 if (prevPoint)
10615 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10616 newPoint->m_lat, newPoint->m_lon,
10617 prevPoint, newPoint, newTrack);
10618
10619 prevPoint = newPoint;
10620 }
10621
10622 g_TrackList.push_back(newTrack);
10623 pConfig->AddNewTrack(newTrack);
10624
10625 gFrame->InvalidateAllGL();
10626 gFrame->RefreshAllCanvas(false);
10627}
10628
10629bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
10630 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
10631 m_pFoundRoutePoint, m_FoundAIS_MMSI,
10632 m_pIDXCandidate);
10633
10634 Connect(
10635 wxEVT_COMMAND_MENU_SELECTED,
10636 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10637
10638 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
10639
10640 Disconnect(
10641 wxEVT_COMMAND_MENU_SELECTED,
10642 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10643
10644 delete m_canvasMenu;
10645 m_canvasMenu = NULL;
10646
10647#ifdef __WXQT__
10648 // gFrame->SurfaceToolbar();
10649 // g_MainToolbar->Raise();
10650#endif
10651
10652 return true;
10653}
10654
10655void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
10656 // Pass menu events from the canvas to the menu handler
10657 // This is necessarily in ChartCanvas since that is the menu's parent.
10658 if (m_canvasMenu) {
10659 m_canvasMenu->PopupMenuHandler(event);
10660 }
10661 return;
10662}
10663
10664void ChartCanvas::StartRoute(void) {
10665 // Do not allow more than one canvas to create a route at one time.
10666 if (g_brouteCreating) return;
10667
10668 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
10669
10670 g_brouteCreating = true;
10671 m_routeState = 1;
10672 m_bDrawingRoute = false;
10673 SetCursor(*pCursorPencil);
10674 // SetCanvasToolbarItemState(ID_ROUTE, true);
10675 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
10676
10677 HideGlobalToolbar();
10678
10679#ifdef __ANDROID__
10680 androidSetRouteAnnunciator(true);
10681#endif
10682}
10683
10684void ChartCanvas::FinishRoute(void) {
10685 m_routeState = 0;
10686 m_prev_pMousePoint = NULL;
10687 m_bDrawingRoute = false;
10688
10689 // SetCanvasToolbarItemState(ID_ROUTE, false);
10690 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
10691#ifdef __ANDROID__
10692 androidSetRouteAnnunciator(false);
10693#endif
10694
10695 SetCursor(*pCursorArrow);
10696
10697 if (m_pMouseRoute) {
10698 if (m_bAppendingRoute)
10699 pConfig->UpdateRoute(m_pMouseRoute);
10700 else {
10701 if (m_pMouseRoute->GetnPoints() > 1) {
10702 pConfig->AddNewRoute(m_pMouseRoute);
10703 } else {
10704 g_pRouteMan->DeleteRoute(m_pMouseRoute,
10705 NavObjectChanges::getInstance());
10706 m_pMouseRoute = NULL;
10707 }
10708 }
10709 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
10710
10711 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
10712 (pRoutePropDialog->IsShown())) {
10713 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
10714 }
10715
10716 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
10717 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10718 pRouteManagerDialog->UpdateRouteListCtrl();
10719 }
10720 }
10721 m_bAppendingRoute = false;
10722 m_pMouseRoute = NULL;
10723
10724 m_pSelectedRoute = NULL;
10725
10726 undo->InvalidateUndo();
10727 gFrame->RefreshAllCanvas(true);
10728
10729 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
10730
10731 ShowGlobalToolbar();
10732
10733 g_brouteCreating = false;
10734}
10735
10736void ChartCanvas::HideGlobalToolbar() {
10737 if (m_canvasIndex == 0) {
10738 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
10739 }
10740}
10741
10742void ChartCanvas::ShowGlobalToolbar() {
10743 if (m_canvasIndex == 0) {
10744 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
10745 }
10746}
10747
10748void ChartCanvas::ShowAISTargetList(void) {
10749 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
10750 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
10751 }
10752
10753 g_pAISTargetList->UpdateAISTargetList();
10754}
10755
10756void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
10757 if (!m_bShowOutlines) return;
10758
10759 if (!ChartData) return;
10760
10761 int nEntry = ChartData->GetChartTableEntries();
10762
10763 for (int i = 0; i < nEntry; i++) {
10764 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
10765
10766 // Check to see if the candidate chart is in the currently active group
10767 bool b_group_draw = false;
10768 if (m_groupIndex > 0) {
10769 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
10770 int index = pt->GetGroupArray()[ig];
10771 if (m_groupIndex == index) {
10772 b_group_draw = true;
10773 break;
10774 }
10775 }
10776 } else
10777 b_group_draw = true;
10778
10779 if (b_group_draw) RenderChartOutline(dc, i, vp);
10780 }
10781
10782 // On CM93 Composite Charts, draw the outlines of the next smaller
10783 // scale cell
10784 cm93compchart *pcm93 = NULL;
10785 if (VPoint.b_quilt) {
10786 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
10787 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
10788 pcm93 = (cm93compchart *)pch;
10789 break;
10790 }
10791 } else if (m_singleChart &&
10792 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
10793 pcm93 = (cm93compchart *)m_singleChart;
10794
10795 if (pcm93) {
10796 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
10797 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
10798
10799 if (zoom_factor > 8.0) {
10800 wxPen mPen(GetGlobalColor(_T("UINFM")), 2, wxPENSTYLE_SHORT_DASH);
10801 dc.SetPen(mPen);
10802 } else {
10803 wxPen mPen(GetGlobalColor(_T("UINFM")), 1, wxPENSTYLE_SOLID);
10804 dc.SetPen(mPen);
10805 }
10806
10807 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
10808 }
10809}
10810
10811void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
10812#ifdef ocpnUSE_GL
10813 if (g_bopengl && m_glcc) {
10814 /* opengl version specially optimized */
10815 m_glcc->RenderChartOutline(dc, dbIndex, vp);
10816 return;
10817 }
10818#endif
10819
10820 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
10821 if (!ChartData->IsChartAvailable(dbIndex)) return;
10822 }
10823
10824 float plylat, plylon;
10825 float plylat1, plylon1;
10826
10827 int pixx, pixy, pixx1, pixy1;
10828
10829 LLBBox box;
10830 ChartData->GetDBBoundingBox(dbIndex, box);
10831
10832 // Don't draw an outline in the case where the chart covers the entire world
10833 // */
10834 if (box.GetLonRange() == 360) return;
10835
10836 double lon_bias = 0;
10837 // chart is outside of viewport lat/lon bounding box
10838 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
10839
10840 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10841
10842 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
10843 dc.SetPen(wxPen(GetGlobalColor(_T ( "YELO1" )), 1, wxPENSTYLE_SOLID));
10844
10845 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
10846 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFG" )), 1, wxPENSTYLE_SOLID));
10847
10848 else
10849 dc.SetPen(wxPen(GetGlobalColor(_T ( "UINFR" )), 1, wxPENSTYLE_SOLID));
10850
10851 // Are there any aux ply entries?
10852 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
10853 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
10854 {
10855 wxPoint r, r1;
10856
10857 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
10858 plylon += lon_bias;
10859
10860 GetCanvasPointPix(plylat, plylon, &r);
10861 pixx = r.x;
10862 pixy = r.y;
10863
10864 for (int i = 0; i < nPly - 1; i++) {
10865 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
10866 plylon1 += lon_bias;
10867
10868 GetCanvasPointPix(plylat1, plylon1, &r1);
10869 pixx1 = r1.x;
10870 pixy1 = r1.y;
10871
10872 int pixxs1 = pixx1;
10873 int pixys1 = pixy1;
10874
10875 bool b_skip = false;
10876
10877 if (vp.chart_scale > 5e7) {
10878 // calculate projected distance between these two points in meters
10879 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
10880 pow((double)(pixy1 - pixy), 2)) /
10881 vp.view_scale_ppm;
10882
10883 if (dist > 0.0) {
10884 // calculate GC distance between these two points in meters
10885 double distgc =
10886 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
10887
10888 // If the distances are nonsense, it means that the scale is very
10889 // small and the segment wrapped the world So skip it....
10890 // TODO improve this to draw two segments
10891 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
10892 b_skip = true;
10893 } else
10894 b_skip = true;
10895 }
10896
10897 ClipResult res = cohen_sutherland_line_clip_i(
10898 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
10899 if (res != Invisible && !b_skip)
10900 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
10901
10902 plylat = plylat1;
10903 plylon = plylon1;
10904 pixx = pixxs1;
10905 pixy = pixys1;
10906 }
10907
10908 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
10909 plylon1 += lon_bias;
10910
10911 GetCanvasPointPix(plylat1, plylon1, &r1);
10912 pixx1 = r1.x;
10913 pixy1 = r1.y;
10914
10915 ClipResult res = cohen_sutherland_line_clip_i(
10916 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
10917 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
10918 }
10919
10920 else // Use Aux PlyPoints
10921 {
10922 wxPoint r, r1;
10923
10924 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
10925 for (int j = 0; j < nAuxPlyEntries; j++) {
10926 int nAuxPly =
10927 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
10928 GetCanvasPointPix(plylat, plylon, &r);
10929 pixx = r.x;
10930 pixy = r.y;
10931
10932 for (int i = 0; i < nAuxPly - 1; i++) {
10933 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
10934
10935 GetCanvasPointPix(plylat1, plylon1, &r1);
10936 pixx1 = r1.x;
10937 pixy1 = r1.y;
10938
10939 int pixxs1 = pixx1;
10940 int pixys1 = pixy1;
10941
10942 bool b_skip = false;
10943
10944 if (vp.chart_scale > 5e7) {
10945 // calculate projected distance between these two points in meters
10946 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
10947 ((pixy1 - pixy) * (pixy1 - pixy))) /
10948 vp.view_scale_ppm;
10949 if (dist > 0.0) {
10950 // calculate GC distance between these two points in meters
10951 double distgc =
10952 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
10953
10954 // If the distances are nonsense, it means that the scale is very
10955 // small and the segment wrapped the world So skip it....
10956 // TODO improve this to draw two segments
10957 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
10958 b_skip = true;
10959 } else
10960 b_skip = true;
10961 }
10962
10963 ClipResult res = cohen_sutherland_line_clip_i(
10964 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
10965 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
10966
10967 plylat = plylat1;
10968 plylon = plylon1;
10969 pixx = pixxs1;
10970 pixy = pixys1;
10971 }
10972
10973 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
10974 GetCanvasPointPix(plylat1, plylon1, &r1);
10975 pixx1 = r1.x;
10976 pixy1 = r1.y;
10977
10978 ClipResult res = cohen_sutherland_line_clip_i(
10979 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
10980 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
10981 }
10982 }
10983}
10984
10985static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
10986 const wxString &second) {
10987 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
10988
10989 int pointsize = dFont->GetPointSize();
10990 pointsize /= OCPN_GetWinDIPScaleFactor();
10991
10992 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
10993 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
10994 false, dFont->GetFaceName());
10995
10996 dc.SetFont(*psRLI_font);
10997
10998 int w1, h1;
10999 int w2 = 0;
11000 int h2 = 0;
11001 int h, w;
11002
11003 int xp, yp;
11004 int hilite_offset = 3;
11005#ifdef __WXMAC__
11006 wxScreenDC sdc;
11007 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
11008 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
11009#else
11010 dc.GetTextExtent(first, &w1, &h1);
11011 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
11012#endif
11013
11014 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11015 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11016
11017 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
11018 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11019
11020 h = h1 + h2;
11021
11022 xp = ref_point.x - w;
11023 yp = ref_point.y;
11024 yp += hilite_offset;
11025
11026 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor(_T ( "YELO1" )), 172);
11027
11028 dc.SetPen(wxPen(GetGlobalColor(_T ( "UBLCK" ))));
11029 dc.SetTextForeground(GetGlobalColor(_T ( "UBLCK" )));
11030
11031 dc.DrawText(first, xp, yp);
11032 if (second.Len()) dc.DrawText(second, xp, yp + h1);
11033}
11034
11035void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11036 if (!g_bAllowShipToActive) return;
11037
11038 Route *rt = g_pRouteMan->GetpActiveRoute();
11039 if (!rt) return;
11040
11041 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11042 wxPoint2DDouble pa, pb;
11043 GetDoubleCanvasPointPix(gLat, gLon, &pa);
11044 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11045
11046 // set pen
11047 int width =
11048 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11049 if (rt->m_width != wxPENSTYLE_INVALID)
11050 width = rt->m_width; // set route pen style if any
11051 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11052 g_shipToActiveStyle, 5)]; // get setting pen style
11053 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11054 wxColour color =
11055 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11056 : // set setting route pen color
11057 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11058 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11059
11060 dc.SetPen(*mypen);
11061 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11062
11063 if (!Use_Opengl)
11064 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11065 (int)pb.m_y, GetVP(), true);
11066
11067#ifdef ocpnUSE_GL
11068 else {
11069#ifdef USE_ANDROID_GLES2
11070 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11071#else
11072 if (style != wxPENSTYLE_SOLID) {
11073 if (glChartCanvas::dash_map.find(style) !=
11074 glChartCanvas::dash_map.end()) {
11075 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11076 dc.SetPen(*mypen);
11077 }
11078 }
11079 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11080#endif
11081
11082 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11083 (int)pb.m_x, (int)pb.m_y, GetVP());
11084 }
11085#endif
11086 }
11087}
11088
11089void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11090 Route *route = 0;
11091 if (m_routeState >= 2) route = m_pMouseRoute;
11092 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11093 route = m_pMeasureRoute;
11094
11095 if (!route) return;
11096
11097 // Validate route pointer
11098 if (!g_pRouteMan->IsRouteValid(route)) return;
11099
11100 double render_lat = m_cursor_lat;
11101 double render_lon = m_cursor_lon;
11102
11103 int np = route->GetnPoints();
11104 if (np) {
11105 if (g_btouch && (np > 1)) np--;
11106 RoutePoint rp = route->GetPoint(np);
11107 render_lat = rp.m_lat;
11108 render_lon = rp.m_lon;
11109 }
11110
11111 double rhumbBearing, rhumbDist;
11112 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11113 &rhumbBearing, &rhumbDist);
11114 double brg = rhumbBearing;
11115 double dist = rhumbDist;
11116
11117 // Skip GreatCircle rubberbanding on touch devices.
11118 if (!g_btouch) {
11119 double gcBearing, gcBearing2, gcDist;
11120 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11121 m_cursor_lat, &gcDist, &gcBearing,
11122 &gcBearing2);
11123 double gcDistm = gcDist / 1852.0;
11124
11125 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11126 rhumbBearing = 90.;
11127
11128 wxPoint destPoint, lastPoint;
11129
11130 route->m_NextLegGreatCircle = false;
11131 int milesDiff = rhumbDist - gcDistm;
11132 if (milesDiff > 1) {
11133 brg = gcBearing;
11134 dist = gcDistm;
11135 route->m_NextLegGreatCircle = true;
11136 }
11137
11138 // FIXME (MacOS, the first segment is rendered wrong)
11139 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11140 &lastPoint);
11141
11142 if (route->m_NextLegGreatCircle) {
11143 for (int i = 1; i <= milesDiff; i++) {
11144 double p = (double)i * (1.0 / (double)milesDiff);
11145 double pLat, pLon;
11146 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11147 &pLon, &pLat, &gcBearing2);
11148 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11149 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11150 false);
11151 lastPoint = destPoint;
11152 }
11153 } else {
11154 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11155 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11156 false);
11157 if (m_bMeasure_DistCircle) {
11158 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11159 powf((float)(r_rband.y - lastPoint.y), 2));
11160
11161 dc.SetPen(*g_pRouteMan->GetRoutePen());
11162 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11163 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11164 }
11165 }
11166 }
11167 }
11168
11169 wxString routeInfo;
11170 if (g_bShowTrue)
11171 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11172 0x00B0);
11173
11174 if (g_bShowMag) {
11175 double latAverage = (m_cursor_lat + render_lat) / 2;
11176 double lonAverage = (m_cursor_lon + render_lon) / 2;
11177 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11178
11179 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11180 (int)varBrg, 0x00B0);
11181 }
11182
11183 routeInfo << _T(" ") << FormatDistanceAdaptive(dist);
11184
11185 wxString s0;
11186 if (!route->m_bIsInLayer)
11187 s0.Append(_("Route") + _T(": "));
11188 else
11189 s0.Append(_("Layer Route: "));
11190
11191 double disp_length = route->m_route_length;
11192 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11193 s0 += FormatDistanceAdaptive(disp_length);
11194
11195 RouteLegInfo(dc, r_rband, routeInfo, s0);
11196
11197 m_brepaint_piano = true;
11198}
11199
11200void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11201 if (!m_bShowVisibleSectors) return;
11202
11203 if (g_bDeferredInitDone) {
11204 // need to re-evaluate sectors?
11205 double rhumbBearing, rhumbDist;
11206 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11207 &rhumbBearing, &rhumbDist);
11208
11209 if (rhumbDist > 0.05) // miles
11210 {
11211 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11212 m_sectorlegsVisible);
11213 m_sector_glat = gLat;
11214 m_sector_glon = gLon;
11215 }
11216 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11217 }
11218}
11219
11220void ChartCanvas::WarpPointerDeferred(int x, int y) {
11221 warp_x = x;
11222 warp_y = y;
11223 warp_flag = true;
11224}
11225
11226int s_msg;
11227
11228void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11229 if (!ps52plib) return;
11230
11231 if (VPoint.b_quilt) { // quilted
11232 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11233
11234 if (m_pQuilt->IsQuiltVector()) {
11235 if (ps52plib->GetStateHash() != m_s52StateHash) {
11236 UpdateS52State();
11237 m_s52StateHash = ps52plib->GetStateHash();
11238 }
11239 }
11240 } else {
11241 if (ps52plib->GetStateHash() != m_s52StateHash) {
11242 UpdateS52State();
11243 m_s52StateHash = ps52plib->GetStateHash();
11244 }
11245 }
11246
11247 // Plugin charts
11248 bool bSendPlibState = true;
11249 if (VPoint.b_quilt) { // quilted
11250 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11251 }
11252
11253 if (bSendPlibState) {
11254 wxJSONValue v;
11255 v[_T("OpenCPN Version Major")] = VERSION_MAJOR;
11256 v[_T("OpenCPN Version Minor")] = VERSION_MINOR;
11257 v[_T("OpenCPN Version Patch")] = VERSION_PATCH;
11258 v[_T("OpenCPN Version Date")] = VERSION_DATE;
11259 v[_T("OpenCPN Version Full")] = VERSION_FULL;
11260
11261 // S52PLIB state
11262 v[_T("OpenCPN S52PLIB ShowText")] = GetShowENCText();
11263 v[_T("OpenCPN S52PLIB ShowSoundings")] = GetShowENCDepth();
11264 v[_T("OpenCPN S52PLIB ShowLights")] = GetShowENCLights();
11265 v[_T("OpenCPN S52PLIB ShowAnchorConditions")] = m_encShowAnchor;
11266 v[_T("OpenCPN S52PLIB ShowQualityOfData")] = GetShowENCDataQual();
11267 v[_T("OpenCPN S52PLIB ShowATONLabel")] = GetShowENCBuoyLabels();
11268 v[_T("OpenCPN S52PLIB ShowLightDescription")] = GetShowENCLightDesc();
11269
11270 v[_T("OpenCPN S52PLIB DisplayCategory")] = GetENCDisplayCategory();
11271
11272 v[_T("OpenCPN S52PLIB SoundingsFactor")] = g_ENCSoundingScaleFactor;
11273 v[_T("OpenCPN S52PLIB TextFactor")] = g_ENCTextScaleFactor;
11274
11275 // Global S52 options
11276
11277 v[_T("OpenCPN S52PLIB MetaDisplay")] = ps52plib->m_bShowMeta;
11278 v[_T("OpenCPN S52PLIB DeclutterText")] = ps52plib->m_bDeClutterText;
11279 v[_T("OpenCPN S52PLIB ShowNationalText")] = ps52plib->m_bShowNationalTexts;
11280 v[_T("OpenCPN S52PLIB ShowImportantTextOnly")] =
11281 ps52plib->m_bShowS57ImportantTextOnly;
11282 v[_T("OpenCPN S52PLIB UseSCAMIN")] = ps52plib->m_bUseSCAMIN;
11283 v[_T("OpenCPN S52PLIB UseSUPER_SCAMIN")] = ps52plib->m_bUseSUPER_SCAMIN;
11284 v[_T("OpenCPN S52PLIB SymbolStyle")] = ps52plib->m_nSymbolStyle;
11285 v[_T("OpenCPN S52PLIB BoundaryStyle")] = ps52plib->m_nBoundaryStyle;
11286 v[_T("OpenCPN S52PLIB ColorShades")] =
11287 S52_getMarinerParam(S52_MAR_TWO_SHADES);
11288
11289 // Some global GUI parameters, for completeness
11290 v[_T("OpenCPN Zoom Mod Vector")] = g_chart_zoom_modifier_vector;
11291 v[_T("OpenCPN Zoom Mod Other")] = g_chart_zoom_modifier_raster;
11292 v[_T("OpenCPN Scale Factor Exp")] =
11293 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11294 v[_T("OpenCPN Display Width")] = (int)g_display_size_mm;
11295
11296 wxJSONWriter w;
11297 wxString out;
11298 w.Write(v, out);
11299
11300 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11301 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11302 g_lastS52PLIBPluginMessage = out;
11303 }
11304 }
11305}
11306int spaint;
11307int s_in_update;
11308void ChartCanvas::OnPaint(wxPaintEvent &event) {
11309 wxPaintDC dc(this);
11310
11311 // GetToolbar()->Show( m_bToolbarEnable );
11312
11313 // Paint updates may have been externally disabled (temporarily, to avoid
11314 // Yield() recursion performance loss) It is important that the wxPaintDC is
11315 // built, even if we elect to not process this paint message. Otherwise, the
11316 // paint message may not be removed from the message queue, esp on Windows.
11317 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11318
11319 if (!m_b_paint_enable) {
11320 return;
11321 }
11322
11323 // If necessary, reconfigure the S52 PLIB
11324 UpdateCanvasS52PLIBConfig();
11325
11326#ifdef ocpnUSE_GL
11327 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11328
11329 if (m_glcc && g_bopengl) {
11330 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11331 s_in_update++;
11332 m_glcc->Update();
11333 s_in_update--;
11334 }
11335
11336 return;
11337 }
11338#endif
11339
11340 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11341
11342 wxRegion ru = GetUpdateRegion();
11343
11344 int rx, ry, rwidth, rheight;
11345 ru.GetBox(rx, ry, rwidth, rheight);
11346 // printf("%d Onpaint update region box: %d %d %d %d\n", spaint++, rx, ry,
11347 // rwidth, rheight);
11348
11349#ifdef ocpnUSE_DIBSECTION
11350 ocpnMemDC temp_dc;
11351#else
11352 wxMemoryDC temp_dc;
11353#endif
11354
11355 long height = GetVP().pix_height;
11356
11357#ifdef __WXMAC__
11358 // On OS X we have to explicitly extend the region for the piano area
11359 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11360 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11361 height += m_Piano->GetHeight();
11362#endif // __WXMAC__
11363 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11364
11365 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11366 if (pthumbwin) {
11367 int thumbx, thumby, thumbsx, thumbsy;
11368 pthumbwin->GetPosition(&thumbx, &thumby);
11369 pthumbwin->GetSize(&thumbsx, &thumbsy);
11370 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11371
11372 if (pthumbwin->IsShown()) {
11373 rgn_chart.Subtract(rgn_thumbwin);
11374 ru.Subtract(rgn_thumbwin);
11375 }
11376 }
11377
11378 // subtract the chart bar if it isn't transparent, and determine if we need to
11379 // paint it
11380 wxRegion rgn_blit = ru;
11381 if (g_bShowChartBar) {
11382 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11383 GetClientSize().x, m_Piano->GetHeight());
11384
11385 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11386 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11387 if (style->chartStatusWindowTransparent)
11388 m_brepaint_piano = true;
11389 else
11390 ru.Subtract(chart_bar_rect);
11391 }
11392 }
11393
11394 if (m_Compass && m_Compass->IsShown()) {
11395 wxRect compassRect = m_Compass->GetRect();
11396 if (ru.Contains(compassRect) != wxOutRegion) {
11397 ru.Subtract(compassRect);
11398 }
11399 }
11400
11401 // Is this viewpoint the same as the previously painted one?
11402 bool b_newview = true;
11403
11404 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11405 (m_cache_vp.rotation == VPoint.rotation) &&
11406 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11407 m_cache_vp.IsValid()) {
11408 b_newview = false;
11409 }
11410
11411 // If the ViewPort is skewed or rotated, we may be able to use the cached
11412 // rotated bitmap.
11413 bool b_rcache_ok = false;
11414 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11415 b_rcache_ok = !b_newview;
11416
11417 // Make a special VP
11418 if (VPoint.b_MercatorProjectionOverride)
11419 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11420 ViewPort svp = VPoint;
11421
11422 svp.pix_width = svp.rv_rect.width;
11423 svp.pix_height = svp.rv_rect.height;
11424
11425 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11426 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11427 // VPoint.rv_rect.height);
11428
11429 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11430
11431 // If we are going to use the cached rotated image, there is no need to fetch
11432 // any chart data and this will do it...
11433 if (b_rcache_ok) chart_get_region.Clear();
11434
11435 // Blit pan acceleration
11436 if (VPoint.b_quilt) // quilted
11437 {
11438 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11439
11440 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11441
11442 bool busy = false;
11443 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11444 m_cache_vp.rotation != VPoint.rotation)) {
11445 AbstractPlatform::ShowBusySpinner();
11446 busy = true;
11447 }
11448
11449 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11450 (m_working_bm.GetHeight() != svp.pix_height))
11451 m_working_bm.Create(svp.pix_width, svp.pix_height,
11452 -1); // make sure the target is big enoug
11453
11454 if (fabs(VPoint.rotation) < 0.01) {
11455 bool b_save = true;
11456
11457 if (g_SencThreadManager) {
11458 if (g_SencThreadManager->GetJobCount()) {
11459 b_save = false;
11460 m_cache_vp.Invalidate();
11461 }
11462 }
11463
11464 // If the saved wxBitmap from last OnPaint is useable
11465 // calculate the blit parameters
11466
11467 // We can only do screen blit painting if subsequent ViewPorts differ by
11468 // whole pixels So, in small scale bFollow mode, force the full screen
11469 // render. This seems a hack....There may be better logic here.....
11470
11471 // if(m_bFollow)
11472 // b_save = false;
11473
11474 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11475 if (b_newview) {
11476 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11477 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11478
11479 int dy = c_new.y - c_old.y;
11480 int dx = c_new.x - c_old.x;
11481
11482 // printf("In OnPaint Trying Blit dx: %d
11483 // dy:%d\n\n", dx, dy);
11484
11485 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11486 if (dx || dy) {
11487 // Blit the reuseable portion of the cached wxBitmap to a working
11488 // bitmap
11489 temp_dc.SelectObject(m_working_bm);
11490
11491 wxMemoryDC cache_dc;
11492 cache_dc.SelectObject(m_cached_chart_bm);
11493
11494 if (dy > 0) {
11495 if (dx > 0) {
11496 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11497 VPoint.pix_height - dy, &cache_dc, dx, dy);
11498 } else {
11499 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11500 VPoint.pix_height - dy, &cache_dc, 0, dy);
11501 }
11502
11503 } else {
11504 if (dx > 0) {
11505 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11506 VPoint.pix_height + dy, &cache_dc, dx, 0);
11507 } else {
11508 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11509 VPoint.pix_height + dy, &cache_dc, 0, 0);
11510 }
11511 }
11512
11513 OCPNRegion update_region;
11514 if (dy) {
11515 if (dy > 0)
11516 update_region.Union(
11517 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11518 else
11519 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11520 }
11521
11522 if (dx) {
11523 if (dx > 0)
11524 update_region.Union(
11525 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11526 else
11527 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11528 }
11529
11530 // Render the new region
11531 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11532 update_region);
11533 cache_dc.SelectObject(wxNullBitmap);
11534 } else {
11535 // No sensible (dx, dy) change in the view, so use the cached
11536 // member bitmap
11537 temp_dc.SelectObject(m_cached_chart_bm);
11538 b_save = false;
11539 }
11540 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11541
11542 } else // not blitable
11543 {
11544 temp_dc.SelectObject(m_working_bm);
11545 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11546 chart_get_region);
11547 }
11548 } else {
11549 // No change in the view, so use the cached member bitmap2
11550 temp_dc.SelectObject(m_cached_chart_bm);
11551 b_save = false;
11552 }
11553 } else // cached bitmap is not yet valid
11554 {
11555 temp_dc.SelectObject(m_working_bm);
11556 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11557 chart_get_region);
11558 }
11559
11560 // Save the fully rendered quilt image as a wxBitmap member of this class
11561 if (b_save) {
11562 // if((m_cached_chart_bm.GetWidth() !=
11563 // svp.pix_width) ||
11564 // (m_cached_chart_bm.GetHeight() !=
11565 // svp.pix_height))
11566 // m_cached_chart_bm.Create(svp.pix_width,
11567 // svp.pix_height, -1); // target wxBitmap
11568 // is big enough
11569 wxMemoryDC scratch_dc_0;
11570 scratch_dc_0.SelectObject(m_cached_chart_bm);
11571 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11572
11573 scratch_dc_0.SelectObject(wxNullBitmap);
11574
11575 m_bm_cache_vp =
11576 VPoint; // save the ViewPort associated with the cached wxBitmap
11577 }
11578 }
11579
11580 else // quilted, rotated
11581 {
11582 temp_dc.SelectObject(m_working_bm);
11583 OCPNRegion chart_get_all_region(
11584 wxRect(0, 0, svp.pix_width, svp.pix_height));
11585 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11586 chart_get_all_region);
11587 }
11588
11589 AbstractPlatform::HideBusySpinner();
11590
11591 }
11592
11593 else // not quilted
11594 {
11595 if (!m_singleChart) {
11596 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
11597 dc.Clear();
11598 return;
11599 }
11600
11601 if (!chart_get_region.IsEmpty()) {
11602 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
11603 }
11604 }
11605
11606 if (temp_dc.IsOk()) {
11607 // Arrange to render the World Chart vector data behind the rendered
11608 // current chart so that uncovered canvas areas show at least the world
11609 // chart.
11610 OCPNRegion chartValidRegion;
11611 if (!VPoint.b_quilt) {
11612 // Make a region covering the current chart on the canvas
11613
11614 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
11615 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
11616 else {
11617 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
11618 // require that the viewport passed here have pix_width and pix_height
11619 // set to the actual display, not the virtual (rv_rect) sizes
11620 // (the vector calculations require the virtual sizes in svp)
11621
11622 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
11623 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
11624 }
11625 } else
11626 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
11627
11628 temp_dc.DestroyClippingRegion();
11629
11630 // Copy current chart region
11631 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
11632
11633 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
11634
11635 if (!backgroundRegion.IsEmpty()) {
11636 // Draw the Background Chart only in the areas NOT covered by the
11637 // current chart view
11638
11639 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
11640 clipping regions with more than 1 rectangle so... */
11641 wxColour water = pWorldBackgroundChart->water;
11642 if (water.IsOk()) {
11643 temp_dc.SetPen(*wxTRANSPARENT_PEN);
11644 temp_dc.SetBrush(wxBrush(water));
11645 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
11646 while (upd.HaveRects()) {
11647 wxRect rect = upd.GetRect();
11648 temp_dc.DrawRectangle(rect);
11649 upd.NextRect();
11650 }
11651 }
11652 // Associate with temp_dc
11653 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
11654 temp_dc.SetDeviceClippingRegion(*clip_region);
11655 delete clip_region;
11656
11657 ocpnDC bgdc(temp_dc);
11658 double r = VPoint.rotation;
11659 SetVPRotation(VPoint.skew);
11660
11661 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
11662 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
11663
11664 SetVPRotation(r);
11665 }
11666 } // temp_dc.IsOk();
11667
11668 wxMemoryDC *pChartDC = &temp_dc;
11669 wxMemoryDC rotd_dc;
11670
11671 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
11672 // Can we use the current rotated image cache?
11673 if (!b_rcache_ok) {
11674#ifdef __WXMSW__
11675 wxMemoryDC tbase_dc;
11676 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
11677 tbase_dc.SelectObject(bm_base);
11678 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11679 tbase_dc.SelectObject(wxNullBitmap);
11680#else
11681 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
11682#endif
11683
11684 wxImage base_image;
11685 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
11686
11687 // Use a local static image rotator to improve wxWidgets code profile
11688 // Especially, on GTK the wxRound and wxRealPoint functions are very
11689 // expensive.....
11690
11691 double angle = GetVP().skew - GetVP().rotation;
11692 wxImage ri;
11693 bool b_rot_ok = false;
11694 if (base_image.IsOk()) {
11695 ViewPort rot_vp = GetVP();
11696
11697 m_b_rot_hidef = false;
11698
11699 ri = Image_Rotate(
11700 base_image, angle,
11701 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
11702 m_b_rot_hidef, &m_roffset);
11703
11704 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11705 (rot_vp.rotation == VPoint.rotation) &&
11706 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
11707 rot_vp.IsValid() && (ri.IsOk())) {
11708 b_rot_ok = true;
11709 }
11710 }
11711
11712 if (b_rot_ok) {
11713 delete m_prot_bm;
11714 m_prot_bm = new wxBitmap(ri);
11715 }
11716
11717 m_roffset.x += VPoint.rv_rect.x;
11718 m_roffset.y += VPoint.rv_rect.y;
11719 }
11720
11721 if (m_prot_bm && m_prot_bm->IsOk()) {
11722 rotd_dc.SelectObject(*m_prot_bm);
11723 pChartDC = &rotd_dc;
11724 } else {
11725 pChartDC = &temp_dc;
11726 m_roffset = wxPoint(0, 0);
11727 }
11728 } else { // unrotated
11729 pChartDC = &temp_dc;
11730 m_roffset = wxPoint(0, 0);
11731 }
11732
11733 wxPoint offset = m_roffset;
11734
11735 // Save the PixelCache viewpoint for next time
11736 m_cache_vp = VPoint;
11737
11738 // Set up a scratch DC for overlay objects
11739 wxMemoryDC mscratch_dc;
11740 mscratch_dc.SelectObject(*pscratch_bm);
11741
11742 mscratch_dc.ResetBoundingBox();
11743 mscratch_dc.DestroyClippingRegion();
11744 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
11745
11746 // Blit the externally invalidated areas of the chart onto the scratch dc
11747 wxRegionIterator upd(rgn_blit); // get the update rect list
11748 while (upd) {
11749 wxRect rect = upd.GetRect();
11750
11751 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
11752 rect.x - offset.x, rect.y - offset.y);
11753 upd++;
11754 }
11755
11756 // If multi-canvas, indicate which canvas has keyboard focus
11757 // by drawing a simple blue bar at the top.
11758 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
11759 if (this == wxWindow::FindFocus()) {
11760 g_focusCanvas = this;
11761
11762 wxColour colour = GetGlobalColor(_T("BLUE4"));
11763 mscratch_dc.SetPen(wxPen(colour));
11764 mscratch_dc.SetBrush(wxBrush(colour));
11765
11766 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
11767 mscratch_dc.DrawRectangle(activeRect);
11768 }
11769 }
11770
11771 // Any MBtiles?
11772 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
11773 unsigned int im = stackIndexArray.size();
11774 if (VPoint.b_quilt && im > 0) {
11775 std::vector<int> tiles_to_show;
11776 for (unsigned int is = 0; is < im; is++) {
11777 const ChartTableEntry &cte =
11778 ChartData->GetChartTableEntry(stackIndexArray[is]);
11779 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
11780 continue;
11781 }
11782 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
11783 tiles_to_show.push_back(stackIndexArray[is]);
11784 }
11785 }
11786
11787 if (tiles_to_show.size())
11788 SetAlertString(_("MBTile requires OpenGL to be enabled"));
11789 }
11790
11791 // May get an unexpected OnPaint call while switching display modes
11792 // Guard for that.
11793 if (!g_bopengl) {
11794 // Draw the rest of the overlay objects directly on the scratch dc
11795 ocpnDC scratch_dc(mscratch_dc);
11796 DrawOverlayObjects(scratch_dc, ru);
11797
11798 if (m_bShowTide) {
11799 RebuildTideSelectList(GetVP().GetBBox());
11800 DrawAllTidesInBBox(scratch_dc, GetVP().GetBBox());
11801 }
11802
11803 if (m_bShowCurrent) {
11804 RebuildCurrentSelectList(GetVP().GetBBox());
11805 DrawAllCurrentsInBBox(scratch_dc, GetVP().GetBBox());
11806 }
11807
11808 if (m_brepaint_piano && g_bShowChartBar) {
11809 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), mscratch_dc);
11810 }
11811
11812 if (m_Compass) m_Compass->Paint(scratch_dc);
11813
11814 RenderAlertMessage(mscratch_dc, GetVP());
11815 }
11816
11817 // quiting?
11818 if (g_bquiting) {
11819#ifdef ocpnUSE_DIBSECTION
11820 ocpnMemDC q_dc;
11821#else
11822 wxMemoryDC q_dc;
11823#endif
11824 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
11825 q_dc.SelectObject(qbm);
11826
11827 // Get a copy of the screen
11828 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
11829
11830 // Draw a rectangle over the screen with a stipple brush
11831 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
11832 q_dc.SetBrush(qbr);
11833 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
11834
11835 // Blit back into source
11836 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
11837 wxCOPY);
11838
11839 q_dc.SelectObject(wxNullBitmap);
11840 }
11841#if 0
11842 // It is possible that this two-step method may be reuired for some platforms.
11843 // So, retain in the code base to aid recovery if necessary
11844
11845 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
11846 if( VPoint.b_quilt ) {
11847 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
11848 ChartBase *chart = m_pQuilt->GetRefChart();
11849 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
11850
11851 // Clear the text Global declutter list
11852 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
11853 if(ChPI)
11854 ChPI->ClearPLIBTextList();
11855 else{
11856 if(ps52plib)
11857 ps52plib->ClearTextList();
11858 }
11859
11860 wxMemoryDC t_dc;
11861 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
11862
11863 wxColor maskBackground = wxColour(1,0,0);
11864 t_dc.SelectObject( qbm );
11865 t_dc.SetBackground(wxBrush(maskBackground));
11866 t_dc.Clear();
11867
11868 // Copy the scratch DC into the new bitmap
11869 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
11870
11871 // Render the text to the new bitmap
11872 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
11873 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
11874
11875 // Copy the new bitmap back to the scratch dc
11876 wxRegionIterator upd_final( ru );
11877 while( upd_final ) {
11878 wxRect rect = upd_final.GetRect();
11879 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
11880 upd_final++;
11881 }
11882
11883 t_dc.SelectObject( wxNullBitmap );
11884 }
11885 }
11886 }
11887#endif
11888 // Direct rendering model...
11889 if (VPoint.b_quilt) {
11890 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
11891 ChartBase *chart = m_pQuilt->GetRefChart();
11892 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
11893 // Clear the text Global declutter list
11894 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
11895 if (ChPI)
11896 ChPI->ClearPLIBTextList();
11897 else {
11898 if (ps52plib) ps52plib->ClearTextList();
11899 }
11900
11901 // Render the text directly to the scratch bitmap
11902 OCPNRegion chart_all_text_region(
11903 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
11904
11905 if (g_bShowChartBar && m_Piano) {
11906 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
11907 GetVP().pix_width, m_Piano->GetHeight());
11908
11909 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11910 if (!style->chartStatusWindowTransparent)
11911 chart_all_text_region.Subtract(chart_bar_rect);
11912 }
11913
11914 if (m_Compass && m_Compass->IsShown()) {
11915 wxRect compassRect = m_Compass->GetRect();
11916 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
11917 chart_all_text_region.Subtract(compassRect);
11918 }
11919 }
11920
11921 mscratch_dc.DestroyClippingRegion();
11922
11923 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
11924 chart_all_text_region);
11925 }
11926 }
11927 }
11928
11929 // And finally, blit the scratch dc onto the physical dc
11930 wxRegionIterator upd_final(rgn_blit);
11931 while (upd_final) {
11932 wxRect rect = upd_final.GetRect();
11933 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
11934 rect.y);
11935 upd_final++;
11936 }
11937
11938 // Test code to validate the dc drawing rectangle....
11939 /*
11940 wxRegionIterator upd_ru ( rgn_blit ); // get the update rect list
11941 while ( upd_ru )
11942 {
11943 wxRect rect = upd_ru.GetRect();
11944
11945 dc.SetPen(wxPen(*wxRED));
11946 dc.SetBrush(wxBrush(*wxRED, wxTRANSPARENT));
11947 dc.DrawRectangle(rect);
11948 upd_ru ++ ;
11949 }
11950 */
11951
11952 // Deselect the chart bitmap from the temp_dc, so that it will not be
11953 // destroyed in the temp_dc dtor
11954 temp_dc.SelectObject(wxNullBitmap);
11955 // And for the scratch bitmap
11956 mscratch_dc.SelectObject(wxNullBitmap);
11957
11958 dc.DestroyClippingRegion();
11959
11960 PaintCleanup();
11961}
11962
11963void ChartCanvas::PaintCleanup() {
11964 // Handle the current graphic window, if present
11965
11966 if (pCwin) {
11967 pCwin->Show();
11968 if (m_bTCupdate) {
11969 pCwin->Refresh();
11970 pCwin->Update();
11971 }
11972 }
11973
11974 // And set flags for next time
11975 m_bTCupdate = false;
11976
11977 // Handle deferred WarpPointer
11978 if (warp_flag) {
11979 WarpPointer(warp_x, warp_y);
11980 warp_flag = false;
11981 }
11982
11983 // Start movement timers, this runs nearly immediately.
11984 // the reason we cannot simply call it directly is the
11985 // refresh events it emits may be blocked from this paint event
11986 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
11987 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
11988}
11989
11990#if 0
11991wxColour GetErrorGraphicColor(double val)
11992{
11993 /*
11994 double valm = wxMin(val_max, val);
11995
11996 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
11997 unsigned char red = (unsigned char)(255 * (valm/val_max));
11998
11999 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12000
12001 hv.saturation = 1.0;
12002 hv.value = 1.0;
12003
12004 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12005 return wxColour(rv.red, rv.green, rv.blue);
12006 */
12007
12008 // HTML colors taken from NOAA WW3 Web representation
12009 wxColour c;
12010 if((val > 0) && (val < 1)) c.Set(_T("#002ad9"));
12011 else if((val >= 1) && (val < 2)) c.Set(_T("#006ed9"));
12012 else if((val >= 2) && (val < 3)) c.Set(_T("#00b2d9"));
12013 else if((val >= 3) && (val < 4)) c.Set(_T("#00d4d4"));
12014 else if((val >= 4) && (val < 5)) c.Set(_T("#00d9a6"));
12015 else if((val >= 5) && (val < 7)) c.Set(_T("#00d900"));
12016 else if((val >= 7) && (val < 9)) c.Set(_T("#95d900"));
12017 else if((val >= 9) && (val < 12)) c.Set(_T("#d9d900"));
12018 else if((val >= 12) && (val < 15)) c.Set(_T("#d9ae00"));
12019 else if((val >= 15) && (val < 18)) c.Set(_T("#d98300"));
12020 else if((val >= 18) && (val < 21)) c.Set(_T("#d95700"));
12021 else if((val >= 21) && (val < 24)) c.Set(_T("#d90000"));
12022 else if((val >= 24) && (val < 27)) c.Set(_T("#ae0000"));
12023 else if((val >= 27) && (val < 30)) c.Set(_T("#8c0000"));
12024 else if((val >= 30) && (val < 36)) c.Set(_T("#870000"));
12025 else if((val >= 36) && (val < 42)) c.Set(_T("#690000"));
12026 else if((val >= 42) && (val < 48)) c.Set(_T("#550000"));
12027 else if( val >= 48) c.Set(_T("#410000"));
12028
12029 return c;
12030}
12031
12032void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12033{
12034 wxImage gr_image(vp->pix_width, vp->pix_height);
12035 gr_image.InitAlpha();
12036
12037 double maxval = -10000;
12038 double minval = 10000;
12039
12040 double rlat, rlon;
12041 double glat, glon;
12042
12043 GetCanvasPixPoint(0, 0, rlat, rlon);
12044
12045 for(int i=1; i < vp->pix_height-1; i++)
12046 {
12047 for(int j=0; j < vp->pix_width; j++)
12048 {
12049 // Reference mercator value
12050// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12051
12052 // Georef value
12053 GetCanvasPixPoint(j, i, glat, glon);
12054
12055 maxval = wxMax(maxval, (glat - rlat));
12056 minval = wxMin(minval, (glat - rlat));
12057
12058 }
12059 rlat = glat;
12060 }
12061
12062 GetCanvasPixPoint(0, 0, rlat, rlon);
12063 for(int i=1; i < vp->pix_height-1; i++)
12064 {
12065 for(int j=0; j < vp->pix_width; j++)
12066 {
12067 // Reference mercator value
12068// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12069
12070 // Georef value
12071 GetCanvasPixPoint(j, i, glat, glon);
12072
12073 double f = ((glat - rlat)-minval)/(maxval - minval);
12074
12075 double dy = (f * 40);
12076
12077 wxColour c = GetErrorGraphicColor(dy);
12078 unsigned char r = c.Red();
12079 unsigned char g = c.Green();
12080 unsigned char b = c.Blue();
12081
12082 gr_image.SetRGB(j, i, r,g,b);
12083 if((glat - rlat )!= 0)
12084 gr_image.SetAlpha(j, i, 128);
12085 else
12086 gr_image.SetAlpha(j, i, 255);
12087
12088 }
12089 rlat = glat;
12090 }
12091
12092 // Create a Bitmap
12093 wxBitmap *pbm = new wxBitmap(gr_image);
12094 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12095 pbm->SetMask(gr_mask);
12096
12097 pmdc->DrawBitmap(*pbm, 0,0);
12098
12099 delete pbm;
12100
12101}
12102
12103#endif
12104
12105void ChartCanvas::CancelMouseRoute() {
12106 m_routeState = 0;
12107 m_pMouseRoute = NULL;
12108 m_bDrawingRoute = false;
12109}
12110
12111int ChartCanvas::GetNextContextMenuId() {
12112 return CanvasMenuHandler::GetNextContextMenuId();
12113}
12114
12115bool ChartCanvas::SetCursor(const wxCursor &c) {
12116#ifdef ocpnUSE_GL
12117 if (g_bopengl && m_glcc)
12118 return m_glcc->SetCursor(c);
12119 else
12120#endif
12121 return wxWindow::SetCursor(c);
12122}
12123
12124void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12125 if (g_bquiting) return;
12126 // Keep the mouse position members up to date
12127 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12128
12129 // Retrigger the route leg popup timer
12130 // This handles the case when the chart is moving in auto-follow mode,
12131 // but no user mouse input is made. The timer handler may Hide() the
12132 // popup if the chart moved enough n.b. We use slightly longer oneshot
12133 // value to allow this method's Refresh() to complete before potentially
12134 // getting another Refresh() in the popup timer handler.
12135 if (!m_RolloverPopupTimer.IsRunning() &&
12136 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12137 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12138 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12139 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12140
12141#ifdef ocpnUSE_GL
12142 if (m_glcc && g_bopengl) {
12143 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12144 // overlay objects.
12145 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12146
12147 m_glcc->Refresh(eraseBackground,
12148 NULL); // We always are going to render the entire screen
12149 // anyway, so make
12150 // sure that the window managers understand the invalid area
12151 // is actually the entire client area.
12152
12153 // We need to selectively Refresh some child windows, if they are visible.
12154 // Note that some children are refreshed elsewhere on timer ticks, so don't
12155 // need attention here.
12156
12157 // Thumbnail chart
12158 if (pthumbwin && pthumbwin->IsShown()) {
12159 pthumbwin->Raise();
12160 pthumbwin->Refresh(false);
12161 }
12162
12163 // ChartInfo window
12164 if (m_pCIWin && m_pCIWin->IsShown()) {
12165 m_pCIWin->Raise();
12166 m_pCIWin->Refresh(false);
12167 }
12168
12169 // if(g_MainToolbar)
12170 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12171
12172 } else
12173#endif
12174 wxWindow::Refresh(eraseBackground, rect);
12175}
12176
12177void ChartCanvas::Update() {
12178 if (m_glcc && g_bopengl) {
12179#ifdef ocpnUSE_GL
12180 m_glcc->Update();
12181#endif
12182 } else
12183 wxWindow::Update();
12184}
12185
12186void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12187 if (!pemboss) return;
12188 int x = pemboss->x, y = pemboss->y;
12189 const double factor = 200;
12190
12191 wxASSERT_MSG(dc.GetDC(), wxT("DrawEmboss has no dc (opengl?)"));
12192 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12193 wxASSERT_MSG(pmdc, wxT("dc to EmbossCanvas not a memory dc"));
12194
12195 // Grab a snipped image out of the chart
12196 wxMemoryDC snip_dc;
12197 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12198 snip_dc.SelectObject(snip_bmp);
12199
12200 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12201 snip_dc.SelectObject(wxNullBitmap);
12202
12203 wxImage snip_img = snip_bmp.ConvertToImage();
12204
12205 // Apply Emboss map to the snip image
12206 unsigned char *pdata = snip_img.GetData();
12207 if (pdata) {
12208 for (int y = 0; y < pemboss->height; y++) {
12209 int map_index = (y * pemboss->width);
12210 for (int x = 0; x < pemboss->width; x++) {
12211 double val = (pemboss->pmap[map_index] * factor) / 256.;
12212
12213 int nred = (int)((*pdata) + val);
12214 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12215 *pdata++ = (unsigned char)nred;
12216
12217 int ngreen = (int)((*pdata) + val);
12218 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12219 *pdata++ = (unsigned char)ngreen;
12220
12221 int nblue = (int)((*pdata) + val);
12222 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12223 *pdata++ = (unsigned char)nblue;
12224
12225 map_index++;
12226 }
12227 }
12228 }
12229
12230 // Convert embossed snip to a bitmap
12231 wxBitmap emb_bmp(snip_img);
12232
12233 // Map to another memoryDC
12234 wxMemoryDC result_dc;
12235 result_dc.SelectObject(emb_bmp);
12236
12237 // Blit to target
12238 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12239
12240 result_dc.SelectObject(wxNullBitmap);
12241}
12242
12243emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12244 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12245
12246 if (GetQuiltMode()) {
12247 // disable Overzoom indicator for MBTiles
12248 int refIndex = GetQuiltRefChartdbIndex();
12249 if (refIndex >= 0) {
12250 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12251 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12252 if (current_type == CHART_TYPE_MBTILES) {
12253 ChartBase *pChart = m_pQuilt->GetRefChart();
12254 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12255 if (ptc) {
12256 zoom_factor = ptc->GetZoomFactor();
12257 }
12258 }
12259 }
12260
12261 if (zoom_factor <= 3.9) return NULL;
12262 } else {
12263 if (m_singleChart) {
12264 if (zoom_factor <= 3.9) return NULL;
12265 } else
12266 return NULL;
12267 }
12268
12269 if (m_pEM_OverZoom) {
12270 m_pEM_OverZoom->x = 4;
12271 m_pEM_OverZoom->y = 0;
12272 if (g_MainToolbar && IsPrimaryCanvas()) {
12273 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12274 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12275 }
12276 }
12277 return m_pEM_OverZoom;
12278}
12279
12280void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12281 GridDraw(dc);
12282
12283 // bool pluginOverlayRender = true;
12284 //
12285 // if(g_canvasConfig > 0){ // Multi canvas
12286 // if(IsPrimaryCanvas())
12287 // pluginOverlayRender = false;
12288 // }
12289
12290 g_overlayCanvas = this;
12291
12292 if (g_pi_manager) {
12293 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12294 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12295 OVERLAY_LEGACY);
12296 }
12297
12298 AISDrawAreaNotices(dc, GetVP(), this);
12299
12300 wxDC *pdc = dc.GetDC();
12301 if (pdc) {
12302 pdc->DestroyClippingRegion();
12303 wxDCClipper(*pdc, ru);
12304 }
12305
12306 if (m_bShowNavobjects) {
12307 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12308 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12309 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12310 DrawAnchorWatchPoints(dc);
12311 } else {
12312 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12313 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12314 }
12315
12316 AISDraw(dc, GetVP(), this);
12317 ShipDraw(dc);
12318 AlertDraw(dc);
12319
12320 RenderVisibleSectorLights(dc);
12321
12322 RenderAllChartOutlines(dc, GetVP());
12323 RenderRouteLegs(dc);
12324 RenderShipToActive(dc, false);
12325 ScaleBarDraw(dc);
12326 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12327 if (g_pi_manager) {
12328 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12329 OVERLAY_OVER_SHIPS);
12330 }
12331
12332 DrawEmboss(dc, EmbossDepthScale());
12333 DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12334 if (g_pi_manager) {
12335 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12336 OVERLAY_OVER_EMBOSS);
12337 }
12338 if (!g_PrintingInProgress) {
12339 if (IsPrimaryCanvas()) {
12340 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12341 }
12342
12343 if (IsPrimaryCanvas()) {
12344 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12345 }
12346
12347 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12348
12349 if (m_pTrackRolloverWin) {
12350 m_pTrackRolloverWin->Draw(dc);
12351 m_brepaint_piano = true;
12352 }
12353
12354 if (m_pRouteRolloverWin) {
12355 m_pRouteRolloverWin->Draw(dc);
12356 m_brepaint_piano = true;
12357 }
12358
12359 if (m_pAISRolloverWin) {
12360 m_pAISRolloverWin->Draw(dc);
12361 m_brepaint_piano = true;
12362 }
12363 }
12364 if (g_pi_manager) {
12365 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12366 OVERLAY_OVER_UI);
12367 }
12368}
12369
12370emboss_data *ChartCanvas::EmbossDepthScale() {
12371 if (!m_bShowDepthUnits) return NULL;
12372
12373 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12374
12375 if (GetQuiltMode()) {
12376 wxString s = m_pQuilt->GetQuiltDepthUnit();
12377 s.MakeUpper();
12378 if (s == _T("FEET"))
12379 depth_unit_type = DEPTH_UNIT_FEET;
12380 else if (s.StartsWith(_T("FATHOMS")))
12381 depth_unit_type = DEPTH_UNIT_FATHOMS;
12382 else if (s.StartsWith(_T("METERS")))
12383 depth_unit_type = DEPTH_UNIT_METERS;
12384 else if (s.StartsWith(_T("METRES")))
12385 depth_unit_type = DEPTH_UNIT_METERS;
12386 else if (s.StartsWith(_T("METRIC")))
12387 depth_unit_type = DEPTH_UNIT_METERS;
12388 else if (s.StartsWith(_T("METER")))
12389 depth_unit_type = DEPTH_UNIT_METERS;
12390
12391 } else {
12392 if (m_singleChart) {
12393 depth_unit_type = m_singleChart->GetDepthUnitType();
12394 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12395 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12396 }
12397 }
12398
12399 emboss_data *ped = NULL;
12400 switch (depth_unit_type) {
12401 case DEPTH_UNIT_FEET:
12402 ped = m_pEM_Feet;
12403 break;
12404 case DEPTH_UNIT_METERS:
12405 ped = m_pEM_Meters;
12406 break;
12407 case DEPTH_UNIT_FATHOMS:
12408 ped = m_pEM_Fathoms;
12409 break;
12410 default:
12411 return NULL;
12412 }
12413
12414 ped->x = (GetVP().pix_width - ped->width);
12415
12416 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12417 wxRect r = m_Compass->GetRect();
12418 ped->y = r.y + r.height;
12419 } else {
12420 ped->y = 40;
12421 }
12422 return ped;
12423}
12424
12425void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12426 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12427 wxFont font;
12428 if (style->embossFont == wxEmptyString) {
12429 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12430 font = *dFont;
12431 font.SetPointSize(60);
12432 font.SetWeight(wxFONTWEIGHT_BOLD);
12433 } else
12434 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12435 wxFONTWEIGHT_BOLD, false, style->embossFont);
12436
12437 int emboss_width = 500;
12438 int emboss_height = 200;
12439
12440 // Free any existing emboss maps
12441 delete m_pEM_Feet;
12442 delete m_pEM_Meters;
12443 delete m_pEM_Fathoms;
12444
12445 // Create the 3 DepthUnit emboss map structures
12446 m_pEM_Feet =
12447 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12448 m_pEM_Meters =
12449 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12450 m_pEM_Fathoms =
12451 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12452}
12453
12454#define OVERZOOM_TEXT _("OverZoom")
12455
12456void ChartCanvas::SetOverzoomFont() {
12457 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12458 int w, h;
12459
12460 wxFont font;
12461 if (style->embossFont == wxEmptyString) {
12462 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12463 font = *dFont;
12464 font.SetPointSize(40);
12465 font.SetWeight(wxFONTWEIGHT_BOLD);
12466 } else
12467 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12468 wxFONTWEIGHT_BOLD, false, style->embossFont);
12469
12470 wxClientDC dc(this);
12471 dc.SetFont(font);
12472 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12473
12474 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12475 font.SetPointSize(font.GetPointSize() - 1);
12476 dc.SetFont(font);
12477 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12478 }
12479 m_overzoomFont = font;
12480 m_overzoomTextWidth = w;
12481 m_overzoomTextHeight = h;
12482}
12483
12484void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12485 delete m_pEM_OverZoom;
12486
12487 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12488 m_pEM_OverZoom =
12489 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12490 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12491}
12492
12493emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12494 int height, const wxString &str,
12495 ColorScheme cs) {
12496 int *pmap;
12497
12498 // Create a temporary bitmap
12499 wxBitmap bmp(width, height, -1);
12500
12501 // Create a memory DC
12502 wxMemoryDC temp_dc;
12503 temp_dc.SelectObject(bmp);
12504
12505 // Paint on it
12506 temp_dc.SetBackground(*wxWHITE_BRUSH);
12507 temp_dc.SetTextBackground(*wxWHITE);
12508 temp_dc.SetTextForeground(*wxBLACK);
12509
12510 temp_dc.Clear();
12511
12512 temp_dc.SetFont(font);
12513
12514 int str_w, str_h;
12515 temp_dc.GetTextExtent(str, &str_w, &str_h);
12516 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12517 temp_dc.DrawText(str, 1, 1);
12518
12519 // Deselect the bitmap
12520 temp_dc.SelectObject(wxNullBitmap);
12521
12522 // Convert bitmap the wxImage for manipulation
12523 wxImage img = bmp.ConvertToImage();
12524
12525 int image_width = str_w * 105 / 100;
12526 int image_height = str_h * 105 / 100;
12527 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12528 wxMin(image_height, img.GetHeight()));
12529 wxImage imgs = img.GetSubImage(r);
12530
12531 double val_factor;
12532 switch (cs) {
12533 case GLOBAL_COLOR_SCHEME_DAY:
12534 default:
12535 val_factor = 1;
12536 break;
12537 case GLOBAL_COLOR_SCHEME_DUSK:
12538 val_factor = .5;
12539 break;
12540 case GLOBAL_COLOR_SCHEME_NIGHT:
12541 val_factor = .25;
12542 break;
12543 }
12544
12545 int val;
12546 int index;
12547 const int w = imgs.GetWidth();
12548 const int h = imgs.GetHeight();
12549 pmap = (int *)calloc(w * h * sizeof(int), 1);
12550 // Create emboss map by differentiating the emboss image
12551 // and storing integer results in pmap
12552 // n.b. since the image is B/W, it is sufficient to check
12553 // one channel (i.e. red) only
12554 for (int y = 1; y < h - 1; y++) {
12555 for (int x = 1; x < w - 1; x++) {
12556 val =
12557 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
12558 val = (int)(val * val_factor);
12559 index = (y * w) + x;
12560 pmap[index] = val;
12561 }
12562 }
12563
12564 emboss_data *pret = new emboss_data;
12565 pret->pmap = pmap;
12566 pret->width = w;
12567 pret->height = h;
12568
12569 return pret;
12570}
12571
12572void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12573 Track *active_track = NULL;
12574 for (Track *pTrackDraw : g_TrackList) {
12575 if (g_pActiveTrack == pTrackDraw) {
12576 active_track = pTrackDraw;
12577 continue;
12578 }
12579
12580 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
12581 }
12582
12583 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12584}
12585
12586void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12587 Track *active_track = NULL;
12588 for (Track *pTrackDraw : g_TrackList) {
12589 if (g_pActiveTrack == pTrackDraw) {
12590 active_track = pTrackDraw;
12591 break;
12592 }
12593 }
12594 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12595}
12596
12597void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12598 Route *active_route = NULL;
12599
12600 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12601 node = node->GetNext()) {
12602 Route *pRouteDraw = node->GetData();
12603 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12604 active_route = pRouteDraw;
12605 continue;
12606 }
12607
12608 // if(m_canvasIndex == 1)
12609 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
12610 }
12611
12612 // Draw any active or selected route (or track) last, so that is is always on
12613 // top
12614 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12615}
12616
12617void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12618 Route *active_route = NULL;
12619
12620 for (wxRouteListNode *node = pRouteList->GetFirst(); node;
12621 node = node->GetNext()) {
12622 Route *pRouteDraw = node->GetData();
12623 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12624 active_route = pRouteDraw;
12625 break;
12626 }
12627 }
12628 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12629}
12630
12631void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12632 if (!pWayPointMan) return;
12633
12634 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12635
12636 while (node) {
12637 RoutePoint *pWP = node->GetData();
12638 if (pWP) {
12639 if (pWP->m_bIsInRoute) {
12640 node = node->GetNext();
12641 continue;
12642 }
12643
12644 /* technically incorrect... waypoint has bounding box */
12645 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
12646 RoutePointGui(*pWP).Draw(dc, this, NULL);
12647 else {
12648 // Are Range Rings enabled?
12649 if (pWP->GetShowWaypointRangeRings() &&
12650 (pWP->GetWaypointRangeRingsNumber() > 0)) {
12651 double factor = 1.00;
12652 if (pWP->GetWaypointRangeRingsStepUnits() ==
12653 1) // convert kilometers to NMi
12654 factor = 1 / 1.852;
12655
12656 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
12657 pWP->GetWaypointRangeRingsStep() / 60.;
12658 radius *= 2; // Fudge factor
12659
12660 LLBBox radar_box;
12661 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
12662 pWP->m_lat + radius, pWP->m_lon + radius);
12663 if (!BltBBox.IntersectOut(radar_box)) {
12664 RoutePointGui(*pWP).Draw(dc, this, NULL);
12665 }
12666 }
12667 }
12668 }
12669
12670 node = node->GetNext();
12671 }
12672}
12673
12674void ChartCanvas::DrawBlinkObjects(void) {
12675 // All RoutePoints
12676 wxRect update_rect;
12677
12678 if (!pWayPointMan) return;
12679
12680 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
12681
12682 while (node) {
12683 RoutePoint *pWP = node->GetData();
12684 if (pWP) {
12685 if (pWP->m_bBlink) {
12686 update_rect.Union(pWP->CurrentRect_in_DC);
12687 }
12688 }
12689
12690 node = node->GetNext();
12691 }
12692 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
12693}
12694
12695void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
12696 // draw anchor watch rings, if activated
12697
12698 if (pAnchorWatchPoint1 || pAnchorWatchPoint2) {
12699 wxPoint r1, r2;
12700 wxPoint lAnchorPoint1, lAnchorPoint2;
12701 double lpp1 = 0.0;
12702 double lpp2 = 0.0;
12703 if (pAnchorWatchPoint1) {
12704 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
12705 GetCanvasPointPix(pAnchorWatchPoint1->m_lat, pAnchorWatchPoint1->m_lon,
12706 &lAnchorPoint1);
12707 }
12708 if (pAnchorWatchPoint2) {
12709 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
12710 GetCanvasPointPix(pAnchorWatchPoint2->m_lat, pAnchorWatchPoint2->m_lon,
12711 &lAnchorPoint2);
12712 }
12713
12714 wxPen ppPeng(GetGlobalColor(_T ( "UGREN" )), 2);
12715 wxPen ppPenr(GetGlobalColor(_T ( "URED" )), 2);
12716
12717 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
12718 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
12719 dc.SetBrush(*ppBrush);
12720
12721 if (lpp1 > 0) {
12722 dc.SetPen(ppPeng);
12723 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12724 }
12725
12726 if (lpp2 > 0) {
12727 dc.SetPen(ppPeng);
12728 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12729 }
12730
12731 if (lpp1 < 0) {
12732 dc.SetPen(ppPenr);
12733 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
12734 }
12735
12736 if (lpp2 < 0) {
12737 dc.SetPen(ppPenr);
12738 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
12739 }
12740 }
12741}
12742
12743double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
12744 double lpp = 0.;
12745 wxPoint r1;
12746 wxPoint lAnchorPoint;
12747 double d1 = 0.0;
12748 double dabs;
12749 double tlat1, tlon1;
12750
12751 if (pAnchorWatchPoint) {
12752 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
12753 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
12754 dabs = fabs(d1 / 1852.);
12755 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
12756 &tlat1, &tlon1);
12757 GetCanvasPointPix(tlat1, tlon1, &r1);
12758 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
12759 &lAnchorPoint);
12760 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
12761 pow((double)(lAnchorPoint.y - r1.y), 2));
12762
12763 // This is an entry watch
12764 if (d1 < 0) lpp = -lpp;
12765 }
12766 return lpp;
12767}
12768
12769//------------------------------------------------------------------------------------------
12770// Tides Support
12771//------------------------------------------------------------------------------------------
12772void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
12773 if (!ptcmgr) return;
12774
12775 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
12776
12777 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12778 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12779 double lon = pIDX->IDX_lon;
12780 double lat = pIDX->IDX_lat;
12781
12782 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12783 if ((type == 't') || (type == 'T')) {
12784 if (BBox.Contains(lat, lon)) {
12785 // Manage the point selection list
12786 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
12787 }
12788 }
12789 }
12790}
12791
12792extern wxDateTime gTimeSource;
12793
12794void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
12795 if (!ptcmgr) return;
12796
12797 wxDateTime this_now = gTimeSource;
12798 bool cur_time = !gTimeSource.IsValid();
12799 if (cur_time) this_now = wxDateTime::Now();
12800 time_t t_this_now = this_now.GetTicks();
12801
12802 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
12803 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
12804 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
12805 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )), 1,
12806 wxPENSTYLE_SOLID);
12807 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
12808 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )), 1,
12809 wxPENSTYLE_SOLID);
12810
12811 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
12812 GetGlobalColor(_T ( "GREEN1" )), wxBRUSHSTYLE_SOLID);
12813 // wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush (
12814 // GetGlobalColor ( _T ( "UINFD" ) ), wxSOLID );
12815 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
12816 GetGlobalColor(cur_time ? _T ( "BLUE2" ) : _T ( "BLUE3" )),
12817 wxBRUSHSTYLE_SOLID);
12818 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
12819 GetGlobalColor(cur_time ? _T ( "YELO1" ) : _T ( "YELO2" )),
12820 wxBRUSHSTYLE_SOLID);
12821
12822 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
12823 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
12824 int font_size = wxMax(10, dFont->GetPointSize());
12825 font_size /= g_Platform->GetDisplayDIPMult(this);
12826 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
12827 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
12828 false, dFont->GetFaceName());
12829
12830 dc.SetPen(*pblack_pen);
12831 dc.SetBrush(*pgreen_brush);
12832
12833 wxBitmap bm;
12834 switch (m_cs) {
12835 case GLOBAL_COLOR_SCHEME_DAY:
12836 bm = m_bmTideDay;
12837 break;
12838 case GLOBAL_COLOR_SCHEME_DUSK:
12839 bm = m_bmTideDusk;
12840 break;
12841 case GLOBAL_COLOR_SCHEME_NIGHT:
12842 bm = m_bmTideNight;
12843 break;
12844 default:
12845 bm = m_bmTideDay;
12846 break;
12847 }
12848
12849 int bmw = bm.GetWidth();
12850 int bmh = bm.GetHeight();
12851
12852 float scale_factor = 1.0;
12853
12854 // Set the onscreen size of the symbol
12855 // Compensate for various display resolutions
12856 float icon_pixelRefDim = 45;
12857
12858#if 0
12859 float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *25 / 1000; // Intended physical rendered size onscreen
12860 nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 8);
12861 nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 15);
12862 float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
12863#endif
12864
12865#ifndef __ANDROID__
12866 // another method is simply to declare that the icon shall be x times the size
12867 // of a raster symbol (e.g.BOYLAT)
12868 // This is a bit of a hack that will suffice until until we get fully
12869 // scalable ENC symbol sets
12870 // float nominal_icon_size_pixels = 48; // 3 x 16
12871 // float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
12872
12873 // or, x times size of text font
12874 wxScreenDC sdc;
12875 int height;
12876 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
12877 height *= g_Platform->GetDisplayDIPMult(this);
12878 float nominal_icon_size_pixels = 48; // 3 x 16
12879 float pix_factor = (2 * height) / nominal_icon_size_pixels;
12880
12881#else
12882 // Yet another method goes like this:
12883 // Set the onscreen size of the symbol
12884 // Compensate for various display resolutions
12885 // Develop empirically, making a symbol about 16 mm tall
12886 double symHeight =
12887 icon_pixelRefDim /
12888 GetPixPerMM(); // from draw instructions, symbol is xx pix high
12889 double targetHeight0 = 16.0;
12890
12891 // But we want to scale the size down for smaller displays
12892 double displaySize = m_display_size_mm;
12893 displaySize = wxMax(displaySize, 100);
12894
12895 float targetHeight = wxMin(targetHeight0, displaySize / 15);
12896
12897 double pix_factor = targetHeight / symHeight;
12898#endif
12899
12900 scale_factor *= pix_factor;
12901
12902 float user_scale_factor = g_ChartScaleFactorExp;
12903 if (g_ChartScaleFactorExp > 1.0)
12904 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
12905 1.2; // soften the scale factor a bit
12906
12907 scale_factor *= user_scale_factor;
12908 scale_factor *= GetContentScaleFactor();
12909
12910 {
12911 double marge = 0.05;
12912 std::vector<LLBBox> drawn_boxes;
12913 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
12914 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
12915
12916 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
12917 if ((type == 't') || (type == 'T')) // only Tides
12918 {
12919 double lon = pIDX->IDX_lon;
12920 double lat = pIDX->IDX_lat;
12921
12922 if (BBox.ContainsMarge(lat, lon, marge)) {
12923 // Avoid drawing detailed graphic for duplicate tide stations
12924 if (GetVP().chart_scale < 500000) {
12925 bool bdrawn = false;
12926 for (size_t i = 0; i < drawn_boxes.size(); i++) {
12927 if (drawn_boxes[i].Contains(lat, lon)) {
12928 bdrawn = true;
12929 break;
12930 }
12931 }
12932 if (bdrawn) continue; // the station loop
12933
12934 LLBBox this_box;
12935 this_box.Set(lat, lon, lat, lon);
12936 this_box.EnLarge(.005);
12937 drawn_boxes.push_back(this_box);
12938 }
12939
12940 wxPoint r;
12941 GetCanvasPointPix(lat, lon, &r);
12942 // draw standard icons
12943 if (GetVP().chart_scale > 500000) {
12944 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
12945 }
12946 // draw "extended" icons
12947 else {
12948 dc.SetFont(*plabelFont);
12949 {
12950 {
12951 float val, nowlev;
12952 float ltleve = 0.;
12953 float htleve = 0.;
12954 time_t tctime;
12955 time_t lttime = 0;
12956 time_t httime = 0;
12957 bool wt;
12958 // define if flood or ebb in the last ten minutes and verify if
12959 // data are useable
12960 if (ptcmgr->GetTideFlowSens(
12961 t_this_now, BACKWARD_TEN_MINUTES_STEP,
12962 pIDX->IDX_rec_num, nowlev, val, wt)) {
12963 // search forward the first HW or LW near "now" ( starting at
12964 // "now" - ten minutes )
12965 ptcmgr->GetHightOrLowTide(
12966 t_this_now + BACKWARD_TEN_MINUTES_STEP,
12967 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
12968 wt, pIDX->IDX_rec_num, val, tctime);
12969 if (wt) {
12970 httime = tctime;
12971 htleve = val;
12972 } else {
12973 lttime = tctime;
12974 ltleve = val;
12975 }
12976 wt = !wt;
12977
12978 // then search opposite tide near "now"
12979 if (tctime > t_this_now) // search backward
12980 ptcmgr->GetHightOrLowTide(
12981 t_this_now, BACKWARD_TEN_MINUTES_STEP,
12982 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
12983 pIDX->IDX_rec_num, val, tctime);
12984 else
12985 // or search forward
12986 ptcmgr->GetHightOrLowTide(
12987 t_this_now, FORWARD_TEN_MINUTES_STEP,
12988 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
12989 val, tctime);
12990 if (wt) {
12991 httime = tctime;
12992 htleve = val;
12993 } else {
12994 lttime = tctime;
12995 ltleve = val;
12996 }
12997
12998 // draw the tide rectangle:
12999
13000 // tide icon rectangle has default pre-scaled width = 12 ,
13001 // height = 45
13002 int width = (int)(12 * scale_factor + 0.5);
13003 int height = (int)(45 * scale_factor + 0.5);
13004 int linew = wxMax(1, (int)(scale_factor));
13005 int xDraw = r.x - (width / 2);
13006 int yDraw = r.y - (height / 2);
13007
13008 // process tide state ( %height and flow sens )
13009 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13010 int hs = (httime > lttime) ? -4 : 4;
13011 hs *= (int)(scale_factor + 0.5);
13012 if (ts > 0.995 || ts < 0.005) hs = 0;
13013 int ht_y = (int)(height * ts);
13014
13015 // draw yellow tide rectangle outlined in black
13016 pblack_pen->SetWidth(linew);
13017 dc.SetPen(*pblack_pen);
13018 dc.SetBrush(*pyelo_brush);
13019 dc.DrawRectangle(xDraw, yDraw, width, height);
13020
13021 // draw blue rectangle as water height, smaller in width than
13022 // yellow rectangle
13023 dc.SetPen(*pblue_pen);
13024 dc.SetBrush(*pblue_brush);
13025 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13026 (width - (4 * linew)), height - ht_y);
13027
13028 // draw sens arrows (ensure they are not "under-drawn" by top
13029 // line of blue rectangle )
13030 int hl;
13031 wxPoint arrow[3];
13032 arrow[0].x = xDraw + 2 * linew;
13033 arrow[1].x = xDraw + width / 2;
13034 arrow[2].x = xDraw + width - 2 * linew;
13035 pyelo_pen->SetWidth(linew);
13036 pblue_pen->SetWidth(linew);
13037 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13038 {
13039 hl = (int)(height * 0.25) + yDraw;
13040 arrow[0].y = hl;
13041 arrow[1].y = hl + hs;
13042 arrow[2].y = hl;
13043 if (ts < 0.15)
13044 dc.SetPen(*pyelo_pen);
13045 else
13046 dc.SetPen(*pblue_pen);
13047 dc.DrawLines(3, arrow);
13048 }
13049 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13050 {
13051 hl = (int)(height * 0.5) + yDraw;
13052 arrow[0].y = hl;
13053 arrow[1].y = hl + hs;
13054 arrow[2].y = hl;
13055 if (ts < 0.40)
13056 dc.SetPen(*pyelo_pen);
13057 else
13058 dc.SetPen(*pblue_pen);
13059 dc.DrawLines(3, arrow);
13060 }
13061 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13062 {
13063 hl = (int)(height * 0.75) + yDraw;
13064 arrow[0].y = hl;
13065 arrow[1].y = hl + hs;
13066 arrow[2].y = hl;
13067 if (ts < 0.65)
13068 dc.SetPen(*pyelo_pen);
13069 else
13070 dc.SetPen(*pblue_pen);
13071 dc.DrawLines(3, arrow);
13072 }
13073 // draw tide level text
13074 wxString s;
13075 s.Printf(_T("%3.1f"), nowlev);
13076 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13077 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13078 int wx1;
13079 dc.GetTextExtent(s, &wx1, NULL);
13080 wx1 *= g_Platform->GetDisplayDIPMult(this);
13081 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13082 }
13083 }
13084 }
13085 }
13086 }
13087 }
13088 }
13089 }
13090}
13091
13092//------------------------------------------------------------------------------------------
13093// Currents Support
13094//------------------------------------------------------------------------------------------
13095
13096void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13097 if (!ptcmgr) return;
13098
13099 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13100
13101 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13102 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13103 double lon = pIDX->IDX_lon;
13104 double lat = pIDX->IDX_lat;
13105
13106 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13107 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13108 if ((BBox.Contains(lat, lon))) {
13109 // Manage the point selection list
13110 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13111 }
13112 }
13113 }
13114}
13115
13116void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13117 if (!ptcmgr) return;
13118
13119 float tcvalue, dir;
13120 bool bnew_val;
13121 char sbuf[20];
13122 wxFont *pTCFont;
13123 double lon_last = 0.;
13124 double lat_last = 0.;
13125 // arrow size for Raz Blanchard : 12 knots north
13126 double marge = 0.2;
13127 bool cur_time = !gTimeSource.IsValid();
13128
13129 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13130 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13131
13132 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(
13133 GetGlobalColor(_T ( "UINFD" )), 1, wxPENSTYLE_SOLID);
13134 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13135 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )), 1,
13136 wxPENSTYLE_SOLID);
13137 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13138 GetGlobalColor(cur_time ? _T ( "UINFO" ) : _T ( "UINFB" )),
13139 wxBRUSHSTYLE_SOLID);
13140 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13141 GetGlobalColor(_T ( "UIBDR" )), wxBRUSHSTYLE_SOLID);
13142 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13143 GetGlobalColor(_T ( "UINFD" )), wxBRUSHSTYLE_SOLID);
13144
13145 double skew_angle = GetVPRotation();
13146
13147 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13148 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13149 int font_size = wxMax(10, dFont->GetPointSize());
13150 font_size /= g_Platform->GetDisplayDIPMult(this);
13151 pTCFont = FontMgr::Get().FindOrCreateFont(
13152 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13153 false, dFont->GetFaceName());
13154
13155 float scale_factor = 1.0;
13156
13157 // Set the onscreen size of the symbol
13158 // Compensate for various display resolutions
13159
13160#if 0
13161 float nominal_icon_size_mm = g_Platform->GetDisplaySizeMM() *3 / 1000; // Intended physical rendered size onscreen
13162 nominal_icon_size_mm = wxMax(nominal_icon_size_mm, 2);
13163 nominal_icon_size_mm = wxMin(nominal_icon_size_mm, 4);
13164 float nominal_icon_size_pixels = wxMax(4.0, floor(g_Platform->GetDisplayDPmm() * nominal_icon_size_mm)); // nominal size, but not less than 4 pixel
13165#endif
13166
13167#if 0
13168 // another method is simply to declare that the icon shall be x times the size of a raster symbol (e.g.BOYLAT)
13169 // This is a bit of a hack that will suffice until until we get fully scalable ENC symbol sets
13170 float nominal_icon_size_pixels = 6; // 16 / 3
13171 float pix_factor = nominal_icon_size_pixels / icon_pixelRefDim;
13172#endif
13173
13174#ifndef __ANDROID__
13175 // or, x times size of text font
13176 wxScreenDC sdc;
13177 int height;
13178 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13179 height *= g_Platform->GetDisplayDIPMult(this);
13180 float nominal_icon_size_pixels = 15;
13181 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13182
13183#else
13184 // Yet another method goes like this:
13185 // Set the onscreen size of the symbol
13186 // Compensate for various display resolutions
13187 // Develop empirically....
13188 float icon_pixelRefDim = 5;
13189
13190 double symHeight =
13191 icon_pixelRefDim /
13192 GetPixPerMM(); // from draw instructions, symbol is xx pix high
13193 double targetHeight0 = 2.0;
13194
13195 // But we want to scale the size down for smaller displays
13196 double displaySize = m_display_size_mm;
13197 displaySize = wxMax(displaySize, 100);
13198
13199 float targetHeight = wxMin(targetHeight0, displaySize / 50);
13200 double pix_factor = targetHeight / symHeight;
13201#endif
13202
13203 scale_factor *= pix_factor;
13204
13205 float user_scale_factor = g_ChartScaleFactorExp;
13206 if (g_ChartScaleFactorExp > 1.0)
13207 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13208 1.2; // soften the scale factor a bit
13209
13210 scale_factor *= user_scale_factor;
13211
13212 scale_factor *= GetContentScaleFactor();
13213
13214 {
13215 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13216 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13217 double lon = pIDX->IDX_lon;
13218 double lat = pIDX->IDX_lat;
13219
13220 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13221 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13222 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13223 wxPoint r;
13224 GetCanvasPointPix(lat, lon, &r);
13225
13226 wxPoint d[4]; // points of a diamond at the current station location
13227 int dd = (int)(5.0 * scale_factor + 0.5);
13228 d[0].x = r.x;
13229 d[0].y = r.y + dd;
13230 d[1].x = r.x + dd;
13231 d[1].y = r.y;
13232 d[2].x = r.x;
13233 d[2].y = r.y - dd;
13234 d[3].x = r.x - dd;
13235 d[3].y = r.y;
13236
13237 if (1) {
13238 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13239 dc.SetPen(*pblack_pen);
13240 dc.SetBrush(*porange_brush);
13241 dc.DrawPolygon(4, d);
13242
13243 if (type == 'C') {
13244 dc.SetBrush(*pblack_brush);
13245 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13246 }
13247
13248 if (GetVP().chart_scale < 1000000) {
13249 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13250 continue;
13251 } else
13252 continue;
13253
13254 if (1 /*type == 'c'*/) {
13255 {
13256 // Get the display pixel location of the current station
13257 int pixxc, pixyc;
13258 pixxc = r.x;
13259 pixyc = r.y;
13260
13261 // Adjust drawing size using logarithmic scale. tcvalue is
13262 // current in knots
13263 double a1 = fabs(tcvalue) * 10.;
13264 // Current values <= 0.1 knot will have no arrow
13265 a1 = wxMax(1.0, a1);
13266 double a2 = log10(a1);
13267
13268 float cscale = scale_factor * a2 * 0.4;
13269
13270 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13271 dc.SetPen(*porange_pen);
13272 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13273 cscale);
13274 // Draw text, if enabled
13275
13276 if (bDrawCurrentValues) {
13277 dc.SetFont(*pTCFont);
13278 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13279 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13280 }
13281 }
13282 } // scale
13283 }
13284 /* This is useful for debugging the TC database
13285 else
13286 {
13287 dc.SetPen ( *porange_pen );
13288 dc.SetBrush ( *pgray_brush );
13289 dc.DrawPolygon ( 4, d );
13290 }
13291 */
13292 }
13293 lon_last = lon;
13294 lat_last = lat;
13295 }
13296 }
13297 }
13298}
13299
13300void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13301 pCwin = new TCWin(this, x, y, pvIDX);
13302}
13303
13304#define NUM_CURRENT_ARROW_POINTS 9
13305static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13306 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13307 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13308 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13309
13310void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13311 double scale) {
13312 if (scale > 1e-2) {
13313 float sin_rot = sin(rot_angle * PI / 180.);
13314 float cos_rot = cos(rot_angle * PI / 180.);
13315
13316 // Move to the first point
13317
13318 float xt = CurrentArrowArray[0].x;
13319 float yt = CurrentArrowArray[0].y;
13320
13321 float xp = (xt * cos_rot) - (yt * sin_rot);
13322 float yp = (xt * sin_rot) + (yt * cos_rot);
13323 int x1 = (int)(xp * scale);
13324 int y1 = (int)(yp * scale);
13325
13326 // Walk thru the point list
13327 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13328 xt = CurrentArrowArray[ip].x;
13329 yt = CurrentArrowArray[ip].y;
13330
13331 float xp = (xt * cos_rot) - (yt * sin_rot);
13332 float yp = (xt * sin_rot) + (yt * cos_rot);
13333 int x2 = (int)(xp * scale);
13334 int y2 = (int)(yp * scale);
13335
13336 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13337
13338 x1 = x2;
13339 y1 = y2;
13340 }
13341 }
13342}
13343
13344wxString ChartCanvas::FindValidUploadPort() {
13345 wxString port;
13346 // Try to use the saved persistent upload port first
13347 if (!g_uploadConnection.IsEmpty() &&
13348 g_uploadConnection.StartsWith(_T("Serial"))) {
13349 port = g_uploadConnection;
13350 }
13351
13352 else {
13353 // If there is no persistent upload port recorded (yet)
13354 // then use the first available serial connection which has output defined.
13355 for (auto *cp : TheConnectionParams()) {
13356 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13357 port << _T("Serial:") << cp->Port;
13358 }
13359 }
13360 return port;
13361}
13362
13363void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13364 if (!win) return;
13365
13366 if (NULL == g_pais_query_dialog_active) {
13367 int pos_x = g_ais_query_dialog_x;
13368 int pos_y = g_ais_query_dialog_y;
13369
13370 if (g_pais_query_dialog_active) {
13371 g_pais_query_dialog_active->Destroy();
13372 g_pais_query_dialog_active = new AISTargetQueryDialog();
13373 } else {
13374 g_pais_query_dialog_active = new AISTargetQueryDialog();
13375 }
13376
13377 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13378 wxPoint(pos_x, pos_y));
13379
13380 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13381 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13382 g_pais_query_dialog_active->SetMMSI(mmsi);
13383 g_pais_query_dialog_active->UpdateText();
13384 wxSize sz = g_pais_query_dialog_active->GetSize();
13385
13386 bool b_reset_pos = false;
13387#ifdef __WXMSW__
13388 // Support MultiMonitor setups which an allow negative window positions.
13389 // If the requested window title bar does not intersect any installed
13390 // monitor, then default to simple primary monitor positioning.
13391 RECT frame_title_rect;
13392 frame_title_rect.left = pos_x;
13393 frame_title_rect.top = pos_y;
13394 frame_title_rect.right = pos_x + sz.x;
13395 frame_title_rect.bottom = pos_y + 30;
13396
13397 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13398 b_reset_pos = true;
13399#else
13400
13401 // Make sure drag bar (title bar) of window intersects wxClient Area of
13402 // screen, with a little slop...
13403 wxRect window_title_rect; // conservative estimate
13404 window_title_rect.x = pos_x;
13405 window_title_rect.y = pos_y;
13406 window_title_rect.width = sz.x;
13407 window_title_rect.height = 30;
13408
13409 wxRect ClientRect = wxGetClientDisplayRect();
13410 ClientRect.Deflate(
13411 60, 60); // Prevent the new window from being too close to the edge
13412 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13413
13414#endif
13415
13416 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13417
13418 } else {
13419 g_pais_query_dialog_active->SetMMSI(mmsi);
13420 g_pais_query_dialog_active->UpdateText();
13421 }
13422
13423 g_pais_query_dialog_active->Show();
13424}
13425
13426void ChartCanvas::ToggleCanvasQuiltMode(void) {
13427 bool cur_mode = GetQuiltMode();
13428
13429 if (!GetQuiltMode())
13430 SetQuiltMode(true);
13431 else if (GetQuiltMode()) {
13432 SetQuiltMode(false);
13433 g_sticky_chart = GetQuiltReferenceChartIndex();
13434 }
13435
13436 if (cur_mode != GetQuiltMode()) {
13437 SetupCanvasQuiltMode();
13438 DoCanvasUpdate();
13439 InvalidateGL();
13440 Refresh();
13441 }
13442 // TODO What to do about this?
13443 // g_bQuiltEnable = GetQuiltMode();
13444
13445 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13446 if (ps52plib) ps52plib->GenerateStateHash();
13447
13448 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13449 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13450}
13451
13452void ChartCanvas::DoCanvasStackDelta(int direction) {
13453 if (!GetQuiltMode()) {
13454 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13455 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13456 if ((current_stack_index + direction) < 0) return;
13457
13458 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13459 int new_dbIndex =
13460 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13461
13462 if (IsChartQuiltableRef(new_dbIndex)) {
13463 ToggleCanvasQuiltMode();
13464 SelectQuiltRefdbChart(new_dbIndex);
13465 m_bpersistent_quilt = false;
13466 }
13467 } else {
13468 SelectChartFromStack(current_stack_index + direction);
13469 }
13470 } else {
13471 std::vector<int> piano_chart_index_array =
13472 GetQuiltExtendedStackdbIndexArray();
13473 int refdb = GetQuiltRefChartdbIndex();
13474
13475 // Find the ref chart in the stack
13476 int current_index = -1;
13477 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13478 if (refdb == piano_chart_index_array[i]) {
13479 current_index = i;
13480 break;
13481 }
13482 }
13483 if (current_index == -1) return;
13484
13485 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13486 int target_family = ctet.GetChartFamily();
13487
13488 int new_index = -1;
13489 int check_index = current_index + direction;
13490 bool found = false;
13491 int check_dbIndex = -1;
13492 int new_dbIndex = -1;
13493
13494 // When quilted. switch within the same chart family
13495 while (!found &&
13496 (unsigned int)check_index < piano_chart_index_array.size() &&
13497 (check_index >= 0)) {
13498 check_dbIndex = piano_chart_index_array[check_index];
13499 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13500 if (target_family == cte.GetChartFamily()) {
13501 found = true;
13502 new_index = check_index;
13503 new_dbIndex = check_dbIndex;
13504 break;
13505 }
13506
13507 check_index += direction;
13508 }
13509
13510 if (!found) return;
13511
13512 if (!IsChartQuiltableRef(new_dbIndex)) {
13513 ToggleCanvasQuiltMode();
13514 SelectdbChart(new_dbIndex);
13515 m_bpersistent_quilt = true;
13516 } else {
13517 SelectQuiltRefChart(new_index);
13518 }
13519 }
13520
13521 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13522 // (checkmarks etc)
13523 SetQuiltChartHiLiteIndex(-1);
13524
13525 ReloadVP();
13526}
13527
13528//--------------------------------------------------------------------------------------------------------
13529//
13530// Toolbar support
13531//
13532//--------------------------------------------------------------------------------------------------------
13533
13534void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13535 // Handle the per-canvas toolbar clicks here
13536
13537 switch (event.GetId()) {
13538 case ID_ZOOMIN: {
13539 StopMovement();
13540 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13541 break;
13542 }
13543
13544 case ID_ZOOMOUT: {
13545 StopMovement();
13546 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13547 break;
13548 }
13549
13550 case ID_STKUP:
13551 DoCanvasStackDelta(1);
13552 DoCanvasUpdate();
13553 break;
13554
13555 case ID_STKDN:
13556 DoCanvasStackDelta(-1);
13557 DoCanvasUpdate();
13558 break;
13559
13560 case ID_FOLLOW: {
13561 TogglebFollow();
13562 break;
13563 }
13564
13565 case ID_CURRENT: {
13566 ShowCurrents(!GetbShowCurrent());
13567 ReloadVP();
13568 Refresh(false);
13569 break;
13570 }
13571
13572 case ID_TIDE: {
13573 ShowTides(!GetbShowTide());
13574 ReloadVP();
13575 Refresh(false);
13576 break;
13577 }
13578
13579 case ID_ROUTE: {
13580 if (0 == m_routeState) {
13581 StartRoute();
13582 } else {
13583 FinishRoute();
13584 }
13585
13586#ifdef __ANDROID__
13587 androidSetRouteAnnunciator(m_routeState == 1);
13588#endif
13589 break;
13590 }
13591
13592 case ID_AIS: {
13593 SetAISCanvasDisplayStyle(-1);
13594 break;
13595 }
13596
13597 default:
13598 break;
13599 }
13600
13601 // And then let gFrame handle the rest....
13602 event.Skip();
13603}
13604
13605void ChartCanvas::SetShowAIS(bool show) {
13606 m_bShowAIS = show;
13607 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13608 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13609}
13610
13611void ChartCanvas::SetAttenAIS(bool show) {
13612 m_bShowAISScaled = show;
13613 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13614 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13615}
13616
13617void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
13618 // make some arrays to hold the dfferences between cycle steps
13619 // show all, scaled, hide all
13620 bool bShowAIS_Array[3] = {true, true, false};
13621 bool bShowScaled_Array[3] = {false, true, true};
13622 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
13623 _("Attenuate less critical AIS targets"),
13624 _("Hide AIS Targets")};
13625 wxString iconName_Array[3] = {_T("AIS"), _T("AIS_Suppressed"),
13626 _T("AIS_Disabled")};
13627 int ArraySize = 3;
13628 int AIS_Toolbar_Switch = 0;
13629 if (StyleIndx == -1) { // -1 means coming from toolbar button
13630 // find current state of switch
13631 for (int i = 1; i < ArraySize; i++) {
13632 if ((bShowAIS_Array[i] == m_bShowAIS) &&
13633 (bShowScaled_Array[i] == m_bShowAISScaled))
13634 AIS_Toolbar_Switch = i;
13635 }
13636 AIS_Toolbar_Switch++; // we did click so continu with next item
13637 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
13638 AIS_Toolbar_Switch++;
13639
13640 } else { // coming from menu bar.
13641 AIS_Toolbar_Switch = StyleIndx;
13642 }
13643 // make sure we are not above array
13644 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
13645
13646 int AIS_Toolbar_Switch_Next =
13647 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
13648 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
13649 AIS_Toolbar_Switch_Next++;
13650 if (AIS_Toolbar_Switch_Next >= ArraySize)
13651 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
13652
13653 // Set found values to global and member variables
13654 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
13655 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
13656}
13657
13658void ChartCanvas::TouchAISToolActive(void) {}
13659
13660void ChartCanvas::UpdateAISTBTool(void) {}
13661
13662//---------------------------------------------------------------------------------
13663//
13664// Compass/GPS status icon support
13665//
13666//---------------------------------------------------------------------------------
13667
13668void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
13669 // Look for change in overlap or positions
13670 bool b_update = false;
13671 int cc1_edge_comp = 2;
13672 wxRect rect = m_Compass->GetRect();
13673 wxSize parent_size = GetSize();
13674
13675 parent_size *= m_displayScale;
13676
13677 // check to see if it would overlap if it was in its home position (upper
13678 // right)
13679 wxPoint tentative_pt(parent_size.x - rect.width - cc1_edge_comp,
13680 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
13681 wxRect tentative_rect(tentative_pt, rect.GetSize());
13682
13683 m_Compass->Move(tentative_pt);
13684
13685 if (m_Compass && m_Compass->IsShown())
13686 m_Compass->UpdateStatus(b_force_new | b_update);
13687
13688 if (b_force_new | b_update) Refresh();
13689}
13690
13691void ChartCanvas::SelectChartFromStack(int index, bool bDir,
13692 ChartTypeEnum New_Type,
13693 ChartFamilyEnum New_Family) {
13694 if (!GetpCurrentStack()) return;
13695 if (!ChartData) return;
13696
13697 if (index < GetpCurrentStack()->nEntry) {
13698 // Open the new chart
13699 ChartBase *pTentative_Chart;
13700 pTentative_Chart = ChartData->OpenStackChartConditional(
13701 GetpCurrentStack(), index, bDir, New_Type, New_Family);
13702
13703 if (pTentative_Chart) {
13704 if (m_singleChart) m_singleChart->Deactivate();
13705
13706 m_singleChart = pTentative_Chart;
13707 m_singleChart->Activate();
13708
13709 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13710 GetpCurrentStack(), m_singleChart->GetFullPath());
13711 }
13712
13713 // Setup the view
13714 double zLat, zLon;
13715 if (m_bFollow) {
13716 zLat = gLat;
13717 zLon = gLon;
13718 } else {
13719 zLat = m_vLat;
13720 zLon = m_vLon;
13721 }
13722
13723 double best_scale_ppm = GetBestVPScale(m_singleChart);
13724 double rotation = GetVPRotation();
13725 double oldskew = GetVPSkew();
13726 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
13727
13728 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
13729 if (fabs(oldskew) > 0.0001) rotation = 0.0;
13730 if (fabs(newskew) > 0.0001) rotation = newskew;
13731 }
13732
13733 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
13734
13735 UpdateGPSCompassStatusBox(true); // Pick up the rotation
13736 }
13737
13738 // refresh Piano
13739 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
13740 if (idx < 0) return;
13741
13742 std::vector<int> piano_active_chart_index_array;
13743 piano_active_chart_index_array.push_back(
13744 GetpCurrentStack()->GetCurrentEntrydbIndex());
13745 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
13746}
13747
13748void ChartCanvas::SelectdbChart(int dbindex) {
13749 if (!GetpCurrentStack()) return;
13750 if (!ChartData) return;
13751
13752 if (dbindex >= 0) {
13753 // Open the new chart
13754 ChartBase *pTentative_Chart;
13755 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
13756
13757 if (pTentative_Chart) {
13758 if (m_singleChart) m_singleChart->Deactivate();
13759
13760 m_singleChart = pTentative_Chart;
13761 m_singleChart->Activate();
13762
13763 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13764 GetpCurrentStack(), m_singleChart->GetFullPath());
13765 }
13766
13767 // Setup the view
13768 double zLat, zLon;
13769 if (m_bFollow) {
13770 zLat = gLat;
13771 zLon = gLon;
13772 } else {
13773 zLat = m_vLat;
13774 zLon = m_vLon;
13775 }
13776
13777 double best_scale_ppm = GetBestVPScale(m_singleChart);
13778
13779 if (m_singleChart)
13780 SetViewPoint(zLat, zLon, best_scale_ppm,
13781 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
13782
13783 // SetChartUpdatePeriod( );
13784
13785 // UpdateGPSCompassStatusBox(); // Pick up the rotation
13786 }
13787
13788 // TODO refresh_Piano();
13789}
13790
13791void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
13792 double target_scale = GetVP().view_scale_ppm;
13793
13794 if (!GetQuiltMode()) {
13795 if (GetpCurrentStack()) {
13796 int stack_index = -1;
13797 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
13798 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
13799 if (check_dbIndex < 0) continue;
13800 const ChartTableEntry &cte =
13801 ChartData->GetChartTableEntry(check_dbIndex);
13802 if (type == cte.GetChartType()) {
13803 stack_index = i;
13804 break;
13805 } else if (family == cte.GetChartFamily()) {
13806 stack_index = i;
13807 break;
13808 }
13809 }
13810
13811 if (stack_index >= 0) {
13812 SelectChartFromStack(stack_index);
13813 }
13814 }
13815 } else {
13816 int sel_dbIndex = -1;
13817 std::vector<int> piano_chart_index_array =
13818 GetQuiltExtendedStackdbIndexArray();
13819 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13820 int check_dbIndex = piano_chart_index_array[i];
13821 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13822 if (type == cte.GetChartType()) {
13823 if (IsChartQuiltableRef(check_dbIndex)) {
13824 sel_dbIndex = check_dbIndex;
13825 break;
13826 }
13827 } else if (family == cte.GetChartFamily()) {
13828 if (IsChartQuiltableRef(check_dbIndex)) {
13829 sel_dbIndex = check_dbIndex;
13830 break;
13831 }
13832 }
13833 }
13834
13835 if (sel_dbIndex >= 0) {
13836 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
13837 // Re-qualify the quilt reference chart selection
13838 AdjustQuiltRefChart();
13839 }
13840
13841 // Now reset the scale to the target...
13842 SetVPScale(target_scale);
13843 }
13844
13845 SetQuiltChartHiLiteIndex(-1);
13846
13847 ReloadVP();
13848}
13849
13850bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
13851 return std::find(m_tile_yesshow_index_array.begin(),
13852 m_tile_yesshow_index_array.end(),
13853 index) != m_tile_yesshow_index_array.end();
13854}
13855
13856bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
13857 return std::find(m_tile_noshow_index_array.begin(),
13858 m_tile_noshow_index_array.end(),
13859 index) != m_tile_noshow_index_array.end();
13860}
13861
13862void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
13863 if (std::find(m_tile_noshow_index_array.begin(),
13864 m_tile_noshow_index_array.end(),
13865 index) == m_tile_noshow_index_array.end()) {
13866 m_tile_noshow_index_array.push_back(index);
13867 }
13868}
13869
13870//-------------------------------------------------------------------------------------------------------
13871//
13872// Piano support
13873//
13874//-------------------------------------------------------------------------------------------------------
13875
13876void ChartCanvas::HandlePianoClick(
13877 int selected_index, const std::vector<int> &selected_dbIndex_array) {
13878 if (g_options && g_options->IsShown())
13879 return; // Piano might be invalid due to chartset updates.
13880 if (!m_pCurrentStack) return;
13881 if (!ChartData) return;
13882
13883 // stop movement or on slow computer we may get something like :
13884 // zoom out with the wheel (timer is set)
13885 // quickly click and display a chart, which may zoom in
13886 // but the delayed timer fires first and it zooms out again!
13887 StopMovement();
13888
13889 // When switching by piano key click, we may appoint the new target chart to
13890 // be any chart in the composite array.
13891 // As an improvement to UX, find the chart that is "closest" to the current
13892 // vp,
13893 // and select that chart. This will cause a jump to the centroid of that
13894 // chart
13895
13896 double distance = 25000; // RTW
13897 int closest_index = -1;
13898 for (int chart_index : selected_dbIndex_array) {
13899 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
13900 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
13901 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
13902
13903 // measure distance as Manhattan style
13904 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
13905 if (test_distance < distance) {
13906 distance = test_distance;
13907 closest_index = chart_index;
13908 }
13909 }
13910
13911 int selected_dbIndex = selected_dbIndex_array[0];
13912 if (closest_index >= 0) selected_dbIndex = closest_index;
13913
13914 if (!GetQuiltMode()) {
13915 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
13916 if (IsChartQuiltableRef(selected_dbIndex)) {
13917 ToggleCanvasQuiltMode();
13918 SelectQuiltRefdbChart(selected_dbIndex);
13919 m_bpersistent_quilt = false;
13920 } else {
13921 SelectChartFromStack(selected_index);
13922 }
13923 } else {
13924 SelectChartFromStack(selected_index);
13925 g_sticky_chart = selected_dbIndex;
13926 }
13927
13928 if (m_singleChart)
13929 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
13930 } else {
13931 // Handle MBTiles overlays first
13932 // Left click simply toggles the noshow array index entry
13933 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
13934 bool bfound = false;
13935 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
13936 if (m_tile_noshow_index_array[i] ==
13937 selected_dbIndex) { // chart is in the noshow list
13938 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
13939 i); // erase it
13940 bfound = true;
13941 break;
13942 }
13943 }
13944 if (!bfound) {
13945 m_tile_noshow_index_array.push_back(selected_dbIndex);
13946 }
13947
13948 // If not already present, add this tileset to the "yes_show" array.
13949 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
13950 m_tile_yesshow_index_array.push_back(selected_dbIndex);
13951 }
13952
13953 else {
13954 if (IsChartQuiltableRef(selected_dbIndex)) {
13955 // if( ChartData ) ChartData->PurgeCache();
13956
13957 // If the chart is a vector chart, and of very large scale,
13958 // then we had better set the new scale directly to avoid excessive
13959 // underzoom on, eg, Inland ENCs
13960 bool set_scale = false;
13961 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
13962 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
13963 set_scale = true;
13964 }
13965 }
13966
13967 if (!set_scale) {
13968 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
13969 } else {
13970 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
13971
13972 // Adjust scale so that the selected chart is underzoomed/overzoomed
13973 // by a controlled amount
13974 ChartBase *pc =
13975 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
13976 if (pc) {
13977 double proposed_scale_onscreen =
13979
13980 if (g_bPreserveScaleOnX) {
13981 proposed_scale_onscreen =
13982 wxMin(proposed_scale_onscreen,
13983 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
13984 GetCanvasWidth()));
13985 } else {
13986 proposed_scale_onscreen =
13987 wxMin(proposed_scale_onscreen,
13988 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
13989 GetCanvasWidth()));
13990
13991 proposed_scale_onscreen =
13992 wxMax(proposed_scale_onscreen,
13993 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
13994 g_b_overzoom_x));
13995 }
13996
13997 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
13998 }
13999 }
14000 } else {
14001 ToggleCanvasQuiltMode();
14002 SelectdbChart(selected_dbIndex);
14003 m_bpersistent_quilt = true;
14004 }
14005 }
14006 }
14007
14008 SetQuiltChartHiLiteIndex(-1);
14009 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14010 // (checkmarks etc)
14011 HideChartInfoWindow();
14012 DoCanvasUpdate();
14013 ReloadVP(); // Pick up the new selections
14014}
14015
14016void ChartCanvas::HandlePianoRClick(
14017 int x, int y, int selected_index,
14018 const std::vector<int> &selected_dbIndex_array) {
14019 if (g_options && g_options->IsShown())
14020 return; // Piano might be invalid due to chartset updates.
14021 if (!GetpCurrentStack()) return;
14022
14023 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14024 UpdateCanvasControlBar();
14025
14026 SetQuiltChartHiLiteIndex(-1);
14027}
14028
14029void ChartCanvas::HandlePianoRollover(
14030 int selected_index, const std::vector<int> &selected_dbIndex_array,
14031 int n_charts, int scale) {
14032 if (g_options && g_options->IsShown())
14033 return; // Piano might be invalid due to chartset updates.
14034 if (!GetpCurrentStack()) return;
14035 if (!ChartData) return;
14036
14037 if (ChartData->IsBusy()) return;
14038
14039 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14040
14041 if (!GetQuiltMode()) {
14042 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14043 } else {
14044 // Select the correct vector
14045 std::vector<int> piano_chart_index_array;
14046 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14047 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14048 if ((GetpCurrentStack()->nEntry > 1) ||
14049 (piano_chart_index_array.size() >= 1)) {
14050 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14051
14052 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14053 ReloadVP(false); // no VP adjustment allowed
14054 } else if (GetpCurrentStack()->nEntry == 1) {
14055 const ChartTableEntry &cte =
14056 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14057 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14058 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14059 ReloadVP(false);
14060 } else if ((-1 == selected_index) &&
14061 (0 == selected_dbIndex_array.size())) {
14062 ShowChartInfoWindow(key_location.x, -1);
14063 }
14064 }
14065 } else {
14066 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14067
14068 if ((GetpCurrentStack()->nEntry > 1) ||
14069 (piano_chart_index_array.size() >= 1)) {
14070 if (n_charts > 1)
14071 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14072 selected_dbIndex_array);
14073 else if (n_charts == 1)
14074 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14075
14076 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14077 ReloadVP(false); // no VP adjustment allowed
14078 }
14079 }
14080 }
14081}
14082
14083void ChartCanvas::ClearPianoRollover() {
14084 ClearQuiltChartHiLiteIndexArray();
14085 ShowChartInfoWindow(0, -1);
14086 std::vector<int> vec;
14087 ShowCompositeInfoWindow(0, 0, 0, vec);
14088 ReloadVP(false);
14089}
14090
14091void ChartCanvas::UpdateCanvasControlBar(void) {
14092 if (m_pianoFrozen) return;
14093
14094 if (!GetpCurrentStack()) return;
14095 if (!ChartData) return;
14096 if (!g_bShowChartBar) return;
14097
14098 int sel_type = -1;
14099 int sel_family = -1;
14100
14101 std::vector<int> piano_chart_index_array;
14102 std::vector<int> empty_piano_chart_index_array;
14103
14104 wxString old_hash = m_Piano->GetStoredHash();
14105
14106 if (GetQuiltMode()) {
14107 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14108 GetQuiltFullScreendbIndexArray());
14109
14110 std::vector<int> piano_active_chart_index_array =
14111 GetQuiltCandidatedbIndexArray();
14112 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14113
14114 std::vector<int> piano_eclipsed_chart_index_array =
14115 GetQuiltEclipsedStackdbIndexArray();
14116 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14117
14118 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14119 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14120
14121 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14122 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14123 } else {
14124 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14125 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14126 // TODO refresh_Piano();
14127
14128 if (m_singleChart) {
14129 sel_type = m_singleChart->GetChartType();
14130 sel_family = m_singleChart->GetChartFamily();
14131 }
14132 }
14133
14134 // Set up the TMerc and Skew arrays
14135 std::vector<int> piano_skew_chart_index_array;
14136 std::vector<int> piano_tmerc_chart_index_array;
14137 std::vector<int> piano_poly_chart_index_array;
14138
14139 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14140 const ChartTableEntry &ctei =
14141 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14142 double skew_norm = ctei.GetChartSkew();
14143 if (skew_norm > 180.) skew_norm -= 360.;
14144
14145 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14146 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14147
14148 // Polyconic skewed charts should show as skewed
14149 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14150 if (fabs(skew_norm) > 1.)
14151 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14152 else
14153 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14154 } else if (fabs(skew_norm) > 1.)
14155 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14156 }
14157 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14158 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14159 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14160
14161 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14162 if (new_hash != old_hash) {
14163 m_Piano->FormatKeys();
14164 HideChartInfoWindow();
14165 m_Piano->ResetRollover();
14166 SetQuiltChartHiLiteIndex(-1);
14167 m_brepaint_piano = true;
14168 }
14169
14170 // Create a bitmask int that describes what Family/Type of charts are shown in
14171 // the bar, and notify the platform.
14172 int mask = 0;
14173 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14174 const ChartTableEntry &ctei =
14175 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14176 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14177 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14178 if (e == CHART_FAMILY_RASTER) mask |= 1;
14179 if (e == CHART_FAMILY_VECTOR) {
14180 if (t == CHART_TYPE_CM93COMP)
14181 mask |= 4;
14182 else
14183 mask |= 2;
14184 }
14185 }
14186
14187 wxString s_indicated;
14188 if (sel_type == CHART_TYPE_CM93COMP)
14189 s_indicated = _T("cm93");
14190 else {
14191 if (sel_family == CHART_FAMILY_RASTER)
14192 s_indicated = _T("raster");
14193 else if (sel_family == CHART_FAMILY_VECTOR)
14194 s_indicated = _T("vector");
14195 }
14196
14197 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14198}
14199
14200void ChartCanvas::FormatPianoKeys(void) { m_Piano->FormatKeys(); }
14201
14202void ChartCanvas::PianoPopupMenu(
14203 int x, int y, int selected_index,
14204 const std::vector<int> &selected_dbIndex_array) {
14205 if (!GetpCurrentStack()) return;
14206
14207 // No context menu if quilting is disabled
14208 if (!GetQuiltMode()) return;
14209
14210 m_piano_ctx_menu = new wxMenu();
14211
14212 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14213 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14214 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14215 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14216 } else {
14217 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14218 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14219 // wxEVT_COMMAND_MENU_SELECTED,
14220 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14221
14222 menu_selected_dbIndex = selected_dbIndex_array[0];
14223 menu_selected_index = selected_index;
14224
14225 // Search the no-show array
14226 bool b_is_in_noshow = false;
14227 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14228 if (m_quilt_noshow_index_array[i] ==
14229 menu_selected_dbIndex) // chart is in the noshow list
14230 {
14231 b_is_in_noshow = true;
14232 break;
14233 }
14234 }
14235
14236 if (b_is_in_noshow) {
14237 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14238 _("Show This Chart"));
14239 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14240 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14241 } else if (GetpCurrentStack()->nEntry > 1) {
14242 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14243 _("Hide This Chart"));
14244 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14245 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14246 }
14247 }
14248
14249 wxPoint pos = wxPoint(x, y - 30);
14250
14251 // Invoke the drop-down menu
14252 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14253 PopupMenu(m_piano_ctx_menu, pos);
14254
14255 delete m_piano_ctx_menu;
14256 m_piano_ctx_menu = NULL;
14257
14258 HideChartInfoWindow();
14259 m_Piano->ResetRollover();
14260
14261 SetQuiltChartHiLiteIndex(-1);
14262 ClearQuiltChartHiLiteIndexArray();
14263
14264 ReloadVP();
14265}
14266
14267void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14268 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14269 if (m_quilt_noshow_index_array[i] ==
14270 menu_selected_dbIndex) // chart is in the noshow list
14271 {
14272 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14273 break;
14274 }
14275 }
14276}
14277
14278void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14279 if (!GetpCurrentStack()) return;
14280 if (!ChartData) return;
14281
14282 RemoveChartFromQuilt(menu_selected_dbIndex);
14283
14284 // It could happen that the chart being disabled is the reference
14285 // chart....
14286 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14287 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14288
14289 int i = menu_selected_index + 1; // select next smaller scale chart
14290 bool b_success = false;
14291 while (i < GetpCurrentStack()->nEntry - 1) {
14292 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14293 if (type == ChartData->GetDBChartType(dbIndex)) {
14294 SelectQuiltRefChart(i);
14295 b_success = true;
14296 break;
14297 }
14298 i++;
14299 }
14300
14301 // If that did not work, try to select the next larger scale compatible
14302 // chart
14303 if (!b_success) {
14304 i = menu_selected_index - 1;
14305 while (i > 0) {
14306 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14307 if (type == ChartData->GetDBChartType(dbIndex)) {
14308 SelectQuiltRefChart(i);
14309 b_success = true;
14310 break;
14311 }
14312 i--;
14313 }
14314 }
14315 }
14316}
14317
14318void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14319 // Remove the item from the list (if it appears) to avoid multiple addition
14320 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14321 if (m_quilt_noshow_index_array[i] ==
14322 dbIndex) // chart is already in the noshow list
14323 {
14324 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14325 break;
14326 }
14327 }
14328
14329 m_quilt_noshow_index_array.push_back(dbIndex);
14330}
14331
14332bool ChartCanvas::UpdateS52State() {
14333 bool retval = false;
14334 // printf(" update %d\n", IsPrimaryCanvas());
14335
14336 if (ps52plib) {
14337 ps52plib->SetShowS57Text(m_encShowText);
14338 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14339 ps52plib->m_bShowSoundg = m_encShowDepth;
14340 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14341 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14342
14343 // Lights
14344 if (!m_encShowLights) // On, going off
14345 ps52plib->AddObjNoshow("LIGHTS");
14346 else // Off, going on
14347 ps52plib->RemoveObjNoshow("LIGHTS");
14348 ps52plib->SetLightsOff(!m_encShowLights);
14349 ps52plib->m_bExtendLightSectors = true;
14350
14351 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14352 ps52plib->SetAnchorOn(m_encShowAnchor);
14353 ps52plib->SetQualityOfData(m_encShowDataQual);
14354 }
14355
14356 return retval;
14357}
14358
14359void ChartCanvas::SetShowENCDataQual(bool show) {
14360 m_encShowDataQual = show;
14361 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14362 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14363
14364 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14365}
14366
14367void ChartCanvas::SetShowENCText(bool show) {
14368 m_encShowText = show;
14369 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14370 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14371
14372 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14373}
14374
14375void ChartCanvas::SetENCDisplayCategory(int category) {
14376 m_encDisplayCategory = category;
14377 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14378}
14379
14380void ChartCanvas::SetShowENCDepth(bool show) {
14381 m_encShowDepth = show;
14382 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14383 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14384
14385 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14386}
14387
14388void ChartCanvas::SetShowENCLightDesc(bool show) {
14389 m_encShowLightDesc = show;
14390 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14391 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14392
14393 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14394}
14395
14396void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14397 m_encShowBuoyLabels = show;
14398 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14399}
14400
14401void ChartCanvas::SetShowENCLights(bool show) {
14402 m_encShowLights = show;
14403 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14404 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14405
14406 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14407}
14408
14409void ChartCanvas::SetShowENCAnchor(bool show) {
14410 m_encShowAnchor = show;
14411 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14412 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14413
14414 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14415}
14416
14417wxRect ChartCanvas::GetMUIBarRect() {
14418 wxRect rv;
14419 if (m_muiBar) {
14420 rv = m_muiBar->GetRect();
14421 }
14422
14423 return rv;
14424}
14425
14426void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14427 if (!GetAlertString().IsEmpty()) {
14428 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14429 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14430
14431 dc.SetFont(*pfont);
14432 dc.SetPen(*wxTRANSPARENT_PEN);
14433
14434 dc.SetBrush(wxColour(243, 229, 47));
14435 int w, h;
14436 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14437 h += 2;
14438 // int yp = vp.pix_height - 20 - h;
14439
14440 wxRect sbr = GetScaleBarRect();
14441 int xp = sbr.x + sbr.width + 10;
14442 int yp = (sbr.y + sbr.height) - h;
14443
14444 int wdraw = w + 10;
14445 dc.DrawRectangle(xp, yp, wdraw, h);
14446 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14447 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14448 }
14449}
14450
14451//--------------------------------------------------------------------------------------------------------
14452// Screen Brightness Control Support Routines
14453//
14454//--------------------------------------------------------------------------------------------------------
14455
14456#ifdef __UNIX__
14457#define BRIGHT_XCALIB
14458#define __OPCPN_USEICC__
14459#endif
14460
14461#ifdef __OPCPN_USEICC__
14462int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14463 double co_green, double co_blue);
14464
14465wxString temp_file_name;
14466#endif
14467
14468#if 0
14469class ocpnCurtain: public wxDialog
14470{
14471 DECLARE_CLASS( ocpnCurtain )
14472 DECLARE_EVENT_TABLE()
14473
14474public:
14475 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14476 ~ocpnCurtain( );
14477 bool ProcessEvent(wxEvent& event);
14478
14479};
14480
14481IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14482
14483BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14484END_EVENT_TABLE()
14485
14486ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14487{
14488 wxDialog::Create( parent, -1, _T("ocpnCurtain"), position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14489}
14490
14491ocpnCurtain::~ocpnCurtain()
14492{
14493}
14494
14495bool ocpnCurtain::ProcessEvent(wxEvent& event)
14496{
14497 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14498 return GetParent()->GetEventHandler()->ProcessEvent(event);
14499}
14500#endif
14501
14502#ifdef _WIN32
14503#include <windows.h>
14504
14505HMODULE hGDI32DLL;
14506typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14507typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14508SetDeviceGammaRamp_ptr_type
14509 g_pSetDeviceGammaRamp; // the API entry points in the dll
14510GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14511
14512WORD *g_pSavedGammaMap;
14513
14514#endif
14515
14516int InitScreenBrightness(void) {
14517#ifdef _WIN32
14518#ifdef ocpnUSE_GL
14519 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14520 HDC hDC;
14521 BOOL bbr;
14522
14523 if (NULL == hGDI32DLL) {
14524 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14525
14526 if (NULL != hGDI32DLL) {
14527 // Get the entry points of the required functions
14528 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14529 hGDI32DLL, "SetDeviceGammaRamp");
14530 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14531 hGDI32DLL, "GetDeviceGammaRamp");
14532
14533 // If the functions are not found, unload the DLL and return false
14534 if ((NULL == g_pSetDeviceGammaRamp) ||
14535 (NULL == g_pGetDeviceGammaRamp)) {
14536 FreeLibrary(hGDI32DLL);
14537 hGDI32DLL = NULL;
14538 return 0;
14539 }
14540 }
14541 }
14542
14543 // Interface is ready, so....
14544 // Get some storage
14545 if (!g_pSavedGammaMap) {
14546 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14547
14548 hDC = GetDC(NULL); // Get the full screen DC
14549 bbr = g_pGetDeviceGammaRamp(
14550 hDC, g_pSavedGammaMap); // Get the existing ramp table
14551 ReleaseDC(NULL, hDC); // Release the DC
14552 }
14553
14554 // On Windows hosts, try to adjust the registry to allow full range
14555 // setting of Gamma table This is an undocumented Windows hack.....
14556 wxRegKey *pRegKey = new wxRegKey(
14557 _T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows ")
14558 _T("NT\\CurrentVersion\\ICM"));
14559 if (!pRegKey->Exists()) pRegKey->Create();
14560 pRegKey->SetValue(_T("GdiIcmGammaRange"), 256);
14561
14562 g_brightness_init = true;
14563 return 1;
14564 }
14565#endif
14566
14567 {
14568 if (NULL == g_pcurtain) {
14569 if (gFrame->CanSetTransparent()) {
14570 // Build the curtain window
14571 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, _T(""),
14572 wxPoint(0, 0), ::wxGetDisplaySize(),
14573 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14574 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14575
14576 // g_pcurtain = new ocpnCurtain(gFrame,
14577 // wxPoint(0,0),::wxGetDisplaySize(),
14578 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14579 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14580
14581 g_pcurtain->Hide();
14582
14583 HWND hWnd = GetHwndOf(g_pcurtain);
14584 SetWindowLong(hWnd, GWL_EXSTYLE,
14585 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14586 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14587 g_pcurtain->SetTransparent(0);
14588
14589 g_pcurtain->Maximize();
14590 g_pcurtain->Show();
14591
14592 // All of this is obtuse, but necessary for Windows...
14593 g_pcurtain->Enable();
14594 g_pcurtain->Disable();
14595
14596 gFrame->Disable();
14597 gFrame->Enable();
14598 // SetFocus();
14599 }
14600 }
14601 g_brightness_init = true;
14602
14603 return 1;
14604 }
14605#else
14606 // Look for "xcalib" application
14607 wxString cmd(_T ( "xcalib -version" ));
14608
14609 wxArrayString output;
14610 long r = wxExecute(cmd, output);
14611 if (0 != r)
14612 wxLogMessage(
14613 _T(" External application \"xcalib\" not found. Screen brightness ")
14614 _T("not changed."));
14615
14616 g_brightness_init = true;
14617 return 0;
14618#endif
14619}
14620
14621int RestoreScreenBrightness(void) {
14622#ifdef _WIN32
14623
14624 if (g_pSavedGammaMap) {
14625 HDC hDC = GetDC(NULL); // Get the full screen DC
14626 g_pSetDeviceGammaRamp(hDC,
14627 g_pSavedGammaMap); // Restore the saved ramp table
14628 ReleaseDC(NULL, hDC); // Release the DC
14629
14630 free(g_pSavedGammaMap);
14631 g_pSavedGammaMap = NULL;
14632 }
14633
14634 if (g_pcurtain) {
14635 g_pcurtain->Close();
14636 g_pcurtain->Destroy();
14637 g_pcurtain = NULL;
14638 }
14639
14640 g_brightness_init = false;
14641 return 1;
14642
14643#endif
14644
14645#ifdef BRIGHT_XCALIB
14646 if (g_brightness_init) {
14647 wxString cmd;
14648 cmd = _T("xcalib -clear");
14649 wxExecute(cmd, wxEXEC_ASYNC);
14650 g_brightness_init = false;
14651 }
14652
14653 return 1;
14654#endif
14655
14656 return 0;
14657}
14658
14659// Set brightness. [0..100]
14660int SetScreenBrightness(int brightness) {
14661#ifdef _WIN32
14662
14663 // Under Windows, we use the SetDeviceGammaRamp function which exists in
14664 // some (most modern?) versions of gdi32.dll Load the required library dll,
14665 // if not already in place
14666#ifdef ocpnUSE_GL
14667 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14668 if (g_pcurtain) {
14669 g_pcurtain->Close();
14670 g_pcurtain->Destroy();
14671 g_pcurtain = NULL;
14672 }
14673
14674 InitScreenBrightness();
14675
14676 if (NULL == hGDI32DLL) {
14677 // Unicode stuff.....
14678 wchar_t wdll_name[80];
14679 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
14680 LPCWSTR cstr = wdll_name;
14681
14682 hGDI32DLL = LoadLibrary(cstr);
14683
14684 if (NULL != hGDI32DLL) {
14685 // Get the entry points of the required functions
14686 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14687 hGDI32DLL, "SetDeviceGammaRamp");
14688 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14689 hGDI32DLL, "GetDeviceGammaRamp");
14690
14691 // If the functions are not found, unload the DLL and return false
14692 if ((NULL == g_pSetDeviceGammaRamp) ||
14693 (NULL == g_pGetDeviceGammaRamp)) {
14694 FreeLibrary(hGDI32DLL);
14695 hGDI32DLL = NULL;
14696 return 0;
14697 }
14698 }
14699 }
14700
14701 HDC hDC = GetDC(NULL); // Get the full screen DC
14702
14703 /*
14704 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
14705 if (cmcap != CM_GAMMA_RAMP)
14706 {
14707 wxLogMessage(_T(" Video hardware does not support brightness control by
14708 gamma ramp adjustment.")); return false;
14709 }
14710 */
14711
14712 int increment = brightness * 256 / 100;
14713
14714 // Build the Gamma Ramp table
14715 WORD GammaTable[3][256];
14716
14717 int table_val = 0;
14718 for (int i = 0; i < 256; i++) {
14719 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
14720 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
14721 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
14722
14723 table_val += increment;
14724
14725 if (table_val > 65535) table_val = 65535;
14726 }
14727
14728 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
14729 ReleaseDC(NULL, hDC); // Release the DC
14730
14731 return 1;
14732 }
14733#endif
14734
14735 {
14736 if (g_pSavedGammaMap) {
14737 HDC hDC = GetDC(NULL); // Get the full screen DC
14738 g_pSetDeviceGammaRamp(hDC,
14739 g_pSavedGammaMap); // Restore the saved ramp table
14740 ReleaseDC(NULL, hDC); // Release the DC
14741 }
14742
14743 if (brightness < 100) {
14744 if (NULL == g_pcurtain) InitScreenBrightness();
14745
14746 if (g_pcurtain) {
14747 int sbrite = wxMax(1, brightness);
14748 sbrite = wxMin(100, sbrite);
14749
14750 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
14751 }
14752 } else {
14753 if (g_pcurtain) {
14754 g_pcurtain->Close();
14755 g_pcurtain->Destroy();
14756 g_pcurtain = NULL;
14757 }
14758 }
14759
14760 return 1;
14761 }
14762
14763#endif
14764
14765#ifdef BRIGHT_XCALIB
14766
14767 if (!g_brightness_init) {
14768 last_brightness = 100;
14769 g_brightness_init = true;
14770 temp_file_name = wxFileName::CreateTempFileName(_T(""));
14771 InitScreenBrightness();
14772 }
14773
14774#ifdef __OPCPN_USEICC__
14775 // Create a dead simple temporary ICC profile file, with gamma ramps set as
14776 // desired, and then activate this temporary profile using xcalib <filename>
14777 if (!CreateSimpleICCProfileFile(
14778 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
14779 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
14780 wxString cmd(_T ( "xcalib " ));
14781 cmd += temp_file_name;
14782
14783 wxExecute(cmd, wxEXEC_ASYNC);
14784 }
14785
14786#else
14787 // Or, use "xcalib -co" to set overall contrast value
14788 // This is not as nice, since the -co parameter wants to be a fraction of
14789 // the current contrast, and values greater than 100 are not allowed. As a
14790 // result, increases of contrast must do a "-clear" step first, which
14791 // produces objectionable flashing.
14792 if (brightness > last_brightness) {
14793 wxString cmd;
14794 cmd = _T("xcalib -clear");
14795 wxExecute(cmd, wxEXEC_ASYNC);
14796
14797 ::wxMilliSleep(10);
14798
14799 int brite_adj = wxMax(1, brightness);
14800 cmd.Printf(_T("xcalib -co %2d -a"), brite_adj);
14801 wxExecute(cmd, wxEXEC_ASYNC);
14802 } else {
14803 int brite_adj = wxMax(1, brightness);
14804 int factor = (brite_adj * 100) / last_brightness;
14805 factor = wxMax(1, factor);
14806 wxString cmd;
14807 cmd.Printf(_T("xcalib -co %2d -a"), factor);
14808 wxExecute(cmd, wxEXEC_ASYNC);
14809 }
14810
14811#endif
14812
14813 last_brightness = brightness;
14814
14815#endif
14816
14817 return 0;
14818}
14819
14820#ifdef __OPCPN_USEICC__
14821
14822#define MLUT_TAG 0x6d4c5554L
14823#define VCGT_TAG 0x76636774L
14824
14825int GetIntEndian(unsigned char *s) {
14826 int ret;
14827 unsigned char *p;
14828 int i;
14829
14830 p = (unsigned char *)&ret;
14831
14832 if (1)
14833 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
14834 else
14835 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
14836
14837 return ret;
14838}
14839
14840unsigned short GetShortEndian(unsigned char *s) {
14841 unsigned short ret;
14842 unsigned char *p;
14843 int i;
14844
14845 p = (unsigned char *)&ret;
14846
14847 if (1)
14848 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
14849 else
14850 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
14851
14852 return ret;
14853}
14854
14855// Create a very simple Gamma correction file readable by xcalib
14856int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14857 double co_green, double co_blue) {
14858 FILE *fp;
14859
14860 if (file_name) {
14861 fp = fopen(file_name, "wb");
14862 if (!fp) return -1; /* file can not be created */
14863 } else
14864 return -1; /* filename char pointer not valid */
14865
14866 // Write header
14867 char header[128];
14868 for (int i = 0; i < 128; i++) header[i] = 0;
14869
14870 fwrite(header, 128, 1, fp);
14871
14872 // Num tags
14873 int numTags0 = 1;
14874 int numTags = GetIntEndian((unsigned char *)&numTags0);
14875 fwrite(&numTags, 1, 4, fp);
14876
14877 int tagName0 = VCGT_TAG;
14878 int tagName = GetIntEndian((unsigned char *)&tagName0);
14879 fwrite(&tagName, 1, 4, fp);
14880
14881 int tagOffset0 = 128 + 4 * sizeof(int);
14882 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
14883 fwrite(&tagOffset, 1, 4, fp);
14884
14885 int tagSize0 = 1;
14886 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
14887 fwrite(&tagSize, 1, 4, fp);
14888
14889 fwrite(&tagName, 1, 4, fp); // another copy of tag
14890
14891 fwrite(&tagName, 1, 4, fp); // dummy
14892
14893 // Table type
14894
14895 /* VideoCardGammaTable (The simplest type) */
14896 int gammatype0 = 0;
14897 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
14898 fwrite(&gammatype, 1, 4, fp);
14899
14900 int numChannels0 = 3;
14901 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
14902 fwrite(&numChannels, 1, 2, fp);
14903
14904 int numEntries0 = 256;
14905 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
14906 fwrite(&numEntries, 1, 2, fp);
14907
14908 int entrySize0 = 1;
14909 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
14910 fwrite(&entrySize, 1, 2, fp);
14911
14912 unsigned char ramp[256];
14913
14914 // Red ramp
14915 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
14916 fwrite(ramp, 256, 1, fp);
14917
14918 // Green ramp
14919 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
14920 fwrite(ramp, 256, 1, fp);
14921
14922 // Blue ramp
14923 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
14924 fwrite(ramp, 256, 1, fp);
14925
14926 fclose(fp);
14927
14928 return 0;
14929}
14930#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:203
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
Chart display canvas.
Definition chcanv.h:135
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:4522
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4518
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:4468
double m_cursor_lat
The latitude at the mouse cursor position in degrees.
Definition chcanv.h:695
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:439
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:470
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2361
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7481
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5019
float GetVPScale()
Return the ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:428
void DoZoomCanvas(double factor, bool can_zoom_to_cursor=true)
Internal function that implements the actual zoom operation.
Definition chcanv.cpp:4624
double GetCanvasTrueScale()
Return the physical pixels per meter at chart center, accounting for latitude distortion.
Definition chcanv.h:444
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4599
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5293
double m_cursor_lon
The longitude at the mouse cursor position in degrees.
Definition chcanv.h:693
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4543
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:4604
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4463
bool SetViewPoint(double lat, double lon)
Set the viewport center point.
Definition chcanv.cpp:5312
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:9798
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
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
Definition route.h:75
bool ActivateNextPoint(Route *pr, bool skipped)
Definition routeman.cpp:365
bool DeleteRoute(Route *pRoute, NavObjectChanges *nav_obj_changes)
Definition routeman.cpp:747
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:52
Class TrackPropDlg.
bool UpdateProperties()
Represents a track, which is a series of connected track points.
Definition track.h:78
Definition undo.h:60
Represents the view port for chart display in OpenCPN.
Definition viewport.h:84
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:199
double ref_scale
The nominal scale of the "reference chart" for this view.
Definition viewport.h:216
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:221
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:209
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:219
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:211
double skew
Angular distortion (shear transform) applied to the viewport in radians.
Definition viewport.h:207
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:104
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:194
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:192
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:214
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.
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